payload-mcp-toolkit 0.8.0 → 0.9.0

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 (101) hide show
  1. package/README.md +100 -237
  2. package/dist/api-keys.js +16 -0
  3. package/dist/api-keys.js.map +1 -1
  4. package/dist/auth-strategy.js +49 -49
  5. package/dist/auth-strategy.js.map +1 -1
  6. package/dist/components/AgentConnectPill.d.ts +3 -0
  7. package/dist/components/AgentConnectPill.js +121 -0
  8. package/dist/components/AgentConnectPill.js.map +1 -0
  9. package/dist/components/CollectionScopesMatrix.js.map +1 -1
  10. package/dist/components/ConsentPermissions.d.ts +10 -0
  11. package/dist/components/ConsentPermissions.js +165 -0
  12. package/dist/components/ConsentPermissions.js.map +1 -0
  13. package/dist/components/GlobalScopesMatrix.js.map +1 -1
  14. package/dist/components/OAuthView.d.ts +4 -0
  15. package/dist/components/OAuthView.js +269 -0
  16. package/dist/components/OAuthView.js.map +1 -0
  17. package/dist/components/ScopesTable.d.ts +28 -0
  18. package/dist/components/ScopesTable.js +165 -130
  19. package/dist/components/ScopesTable.js.map +1 -1
  20. package/dist/components/agentInstructions.d.ts +2 -0
  21. package/dist/components/agentInstructions.js +34 -0
  22. package/dist/components/agentInstructions.js.map +1 -0
  23. package/dist/components/index.d.ts +2 -0
  24. package/dist/components/index.js +2 -0
  25. package/dist/components/index.js.map +1 -1
  26. package/dist/components/oauth.css +111 -0
  27. package/dist/conflict-detection.js +13 -13
  28. package/dist/conflict-detection.js.map +1 -1
  29. package/dist/draft-workflow.js +26 -26
  30. package/dist/draft-workflow.js.map +1 -1
  31. package/dist/endpoint.d.ts +5 -0
  32. package/dist/endpoint.js +21 -13
  33. package/dist/endpoint.js.map +1 -1
  34. package/dist/hash.js +15 -15
  35. package/dist/hash.js.map +1 -1
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.js +67 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/introspection.js +47 -47
  40. package/dist/introspection.js.map +1 -1
  41. package/dist/oauth-permissions.d.ts +52 -0
  42. package/dist/oauth-permissions.js +100 -0
  43. package/dist/oauth-permissions.js.map +1 -0
  44. package/dist/oauth-store.d.ts +19 -0
  45. package/dist/oauth-store.js +122 -0
  46. package/dist/oauth-store.js.map +1 -0
  47. package/dist/oauth.d.ts +19 -0
  48. package/dist/oauth.js +523 -0
  49. package/dist/oauth.js.map +1 -0
  50. package/dist/prompts.js +5 -5
  51. package/dist/prompts.js.map +1 -1
  52. package/dist/registry.js +7 -1
  53. package/dist/registry.js.map +1 -1
  54. package/dist/resources.js +10 -10
  55. package/dist/resources.js.map +1 -1
  56. package/dist/scope/audit-log.js +9 -9
  57. package/dist/scope/audit-log.js.map +1 -1
  58. package/dist/scope/policy.js +7 -7
  59. package/dist/scope/policy.js.map +1 -1
  60. package/dist/tools/_helpers.d.ts +10 -0
  61. package/dist/tools/_helpers.js +53 -41
  62. package/dist/tools/_helpers.js.map +1 -1
  63. package/dist/tools/_layout-helpers.js +33 -33
  64. package/dist/tools/_layout-helpers.js.map +1 -1
  65. package/dist/tools/create-document.js +20 -19
  66. package/dist/tools/create-document.js.map +1 -1
  67. package/dist/tools/delete-document.js +10 -9
  68. package/dist/tools/delete-document.js.map +1 -1
  69. package/dist/tools/find-document.js +18 -15
  70. package/dist/tools/find-document.js.map +1 -1
  71. package/dist/tools/find-global.js +13 -13
  72. package/dist/tools/find-global.js.map +1 -1
  73. package/dist/tools/global-versions.js +8 -7
  74. package/dist/tools/global-versions.js.map +1 -1
  75. package/dist/tools/patch-global-layout.js +9 -9
  76. package/dist/tools/patch-global-layout.js.map +1 -1
  77. package/dist/tools/patch-layout.js +15 -14
  78. package/dist/tools/patch-layout.js.map +1 -1
  79. package/dist/tools/publish-draft.js +2 -1
  80. package/dist/tools/publish-draft.js.map +1 -1
  81. package/dist/tools/publish-global-draft.js +4 -4
  82. package/dist/tools/publish-global-draft.js.map +1 -1
  83. package/dist/tools/resolve-reference.js.map +1 -1
  84. package/dist/tools/safe-delete.js +14 -14
  85. package/dist/tools/safe-delete.js.map +1 -1
  86. package/dist/tools/schedule-publish.js +21 -20
  87. package/dist/tools/schedule-publish.js.map +1 -1
  88. package/dist/tools/search-content.js +12 -12
  89. package/dist/tools/search-content.js.map +1 -1
  90. package/dist/tools/update-document.js +6 -5
  91. package/dist/tools/update-document.js.map +1 -1
  92. package/dist/tools/update-global.js +9 -9
  93. package/dist/tools/update-global.js.map +1 -1
  94. package/dist/tools/upload-media.js +2 -1
  95. package/dist/tools/upload-media.js.map +1 -1
  96. package/dist/tools/versions.js +10 -8
  97. package/dist/tools/versions.js.map +1 -1
  98. package/dist/types.d.ts +3 -0
  99. package/dist/types.js.map +1 -1
  100. package/docs/oauth.md +96 -0
  101. package/package.json +7 -2
@@ -1,10 +1,10 @@
1
1
  import { extractBearerToken, hashKey } from './hash';
2
2
  export const AUTH_STRATEGY_NAME = 'mcp-toolkit-bearer';
3
- /**
4
- * Reads the row's slug, tolerating the pre-0.6 `collection` / `global`
5
- * keys for one release. Logs a one-line warn when the legacy fallback
6
- * fires so operators can spot keys that need re-saving. The fallback is
7
- * scheduled for removal in v0.7.
3
+ /**
4
+ * Reads the row's slug, tolerating the pre-0.6 `collection` / `global`
5
+ * keys for one release. Logs a one-line warn when the legacy fallback
6
+ * fires so operators can spot keys that need re-saving. The fallback is
7
+ * scheduled for removal in v0.7.
8
8
  */ let warnedLegacyShape = false;
9
9
  let warnedLegacyNonCustomOverride = false;
10
10
  /** @internal test-only: reset the one-time legacy warns. */ export function _resetLegacyWarnsForTests() {
@@ -36,40 +36,40 @@ const VALID_GLOBAL_ACTIONS = new Set([
36
36
  'read',
37
37
  'update'
38
38
  ]);
39
- /**
40
- * Builds the runtime `KeyScopes` shape consumed by `registry.assertScopeAllows`
41
- * from the typed scope fields on the api-key row.
42
- *
43
- * Returns null when no typed fields are populated AND no preset is set
44
- * (= full access — back-compat for pre-0.5 rows that pre-date scoped authz).
45
- *
46
- * Two complementary fail-closed rules:
47
- *
48
- * 1. **`'custom'` deny-all sentinel.** `'custom'` is a UI sentinel meaning
49
- * "use my override fields"; it never becomes `KeyScopes.preset` itself.
50
- * Payload persists unset JSON / select fields as `null`, so a fresh
51
- * Custom key with no overrides arrives as `{preset:'custom',
52
- * collectionScopes:null, globalScopes:null, toolAllow:null,
53
- * toolDeny:null}`. That row must deny everything (not fall through to
54
- * full access). The sentinel emits `{collections:{}, globals:{},
55
- * tools:{allow:[]}}`.
56
- *
57
- * 2. **Per-axis explicit-empty, Custom-only.** Under the Custom preset, an
58
- * empty array on any axis (even `[]`) is honoured as written — an empty
59
- * `toolAllow:[]` means "deny all tools on this axis", not "no opinion".
60
- * Under non-Custom presets, empty arrays are IGNORED because Payload's
61
- * hasMany / unpopulated-JSON reads return `[]` for fields the user
62
- * never touched (the override matrices are hidden in the admin UI under
63
- * non-Custom presets via `condition: isCustomPreset`). The on-write
64
- * counterpart of this rule lives in `createApiKeysCollection`'s
65
- * `beforeValidate` hook, which proactively nulls the override axes on
66
- * save when the preset is non-Custom — both layers must stay in sync.
67
- * Non-empty arrays still apply as layered narrowing under any preset.
68
- * `toolDeny` is a deny-list, so an empty array carries no entries — it
69
- * is dropped rather than emitted. NOTE: legacy non-Custom rows persisted
70
- * BEFORE v0.7.1 with populated stale override arrays continue to narrow
71
- * on read until each row is manually re-saved; the on-write fix only
72
- * applies to fresh writes.
39
+ /**
40
+ * Builds the runtime `KeyScopes` shape consumed by `registry.assertScopeAllows`
41
+ * from the typed scope fields on the api-key row.
42
+ *
43
+ * Returns null when no typed fields are populated AND no preset is set
44
+ * (= full access — back-compat for pre-0.5 rows that pre-date scoped authz).
45
+ *
46
+ * Two complementary fail-closed rules:
47
+ *
48
+ * 1. **`'custom'` deny-all sentinel.** `'custom'` is a UI sentinel meaning
49
+ * "use my override fields"; it never becomes `KeyScopes.preset` itself.
50
+ * Payload persists unset JSON / select fields as `null`, so a fresh
51
+ * Custom key with no overrides arrives as `{preset:'custom',
52
+ * collectionScopes:null, globalScopes:null, toolAllow:null,
53
+ * toolDeny:null}`. That row must deny everything (not fall through to
54
+ * full access). The sentinel emits `{collections:{}, globals:{},
55
+ * tools:{allow:[]}}`.
56
+ *
57
+ * 2. **Per-axis explicit-empty, Custom-only.** Under the Custom preset, an
58
+ * empty array on any axis (even `[]`) is honoured as written — an empty
59
+ * `toolAllow:[]` means "deny all tools on this axis", not "no opinion".
60
+ * Under non-Custom presets, empty arrays are IGNORED because Payload's
61
+ * hasMany / unpopulated-JSON reads return `[]` for fields the user
62
+ * never touched (the override matrices are hidden in the admin UI under
63
+ * non-Custom presets via `condition: isCustomPreset`). The on-write
64
+ * counterpart of this rule lives in `createApiKeysCollection`'s
65
+ * `beforeValidate` hook, which proactively nulls the override axes on
66
+ * save when the preset is non-Custom — both layers must stay in sync.
67
+ * Non-empty arrays still apply as layered narrowing under any preset.
68
+ * `toolDeny` is a deny-list, so an empty array carries no entries — it
69
+ * is dropped rather than emitted. NOTE: legacy non-Custom rows persisted
70
+ * BEFORE v0.7.1 with populated stale override arrays continue to narrow
71
+ * on read until each row is manually re-saved; the on-write fix only
72
+ * applies to fresh writes.
73
73
  */ export function composeScopes(row, logger) {
74
74
  const presetRaw = row.preset;
75
75
  const hasPreset = typeof presetRaw === 'string' && presetRaw.length > 0;
@@ -160,13 +160,13 @@ const VALID_GLOBAL_ACTIONS = new Set([
160
160
  }
161
161
  return out;
162
162
  }
163
- /**
164
- * Builds the Payload `auth.strategies` entry that authenticates MCP requests.
165
- *
166
- * Authenticates `Authorization: Bearer <plaintext>` by computing the
167
- * upstream-compatible `apiKeyIndex` HMAC and looking up the row. On match,
168
- * it fires a non-blocking `lastUsedAt` write and hydrates `req.user` with
169
- * the linked user record + key context for downstream scope checks.
163
+ /**
164
+ * Builds the Payload `auth.strategies` entry that authenticates MCP requests.
165
+ *
166
+ * Authenticates `Authorization: Bearer <plaintext>` by computing the
167
+ * upstream-compatible `apiKeyIndex` HMAC and looking up the row. On match,
168
+ * it fires a non-blocking `lastUsedAt` write and hydrates `req.user` with
169
+ * the linked user record + key context for downstream scope checks.
170
170
  */ export function createBearerStrategy(options) {
171
171
  const { collectionSlug, userCollection } = options;
172
172
  return {
@@ -250,9 +250,9 @@ const VALID_GLOBAL_ACTIONS = new Set([
250
250
  }
251
251
  };
252
252
  }
253
- /**
254
- * Reads the per-request API-key context populated by the bearer strategy.
255
- * Returns null for non-MCP requests (e.g. cookie-authenticated admin users).
253
+ /**
254
+ * Reads the per-request API-key context populated by the bearer strategy.
255
+ * Returns null for non-MCP requests (e.g. cookie-authenticated admin users).
256
256
  */ export function getApiKeyContext(req) {
257
257
  const user = req.user;
258
258
  return user?._mcpKey ?? null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth-strategy.ts"],"sourcesContent":["import type { AuthStrategy, PayloadRequest, Where } from 'payload'\r\nimport { extractBearerToken, hashKey } from './hash'\r\nimport type { CollectionAction, GlobalAction, KeyScopes, ScopePreset } from './types'\r\n\r\nexport type { CollectionAction, GlobalAction, KeyScopes, ScopePreset } from './types'\r\n\r\nexport const AUTH_STRATEGY_NAME = 'mcp-toolkit-bearer'\r\n\r\nexport interface CreateBearerStrategyOptions {\r\n /** Slug of the API-keys collection (defaults to `payload-mcp-api-keys`). */\r\n collectionSlug: string\r\n /** Slug of the user collection that API keys link to. */\r\n userCollection: string\r\n}\r\n\r\n/**\r\n * Stored shape for a row in `collectionScopes` / `globalScopes` (v0.6+).\r\n * The parent field name encodes the axis — collection-vs-global is not\r\n * repeated in the row payload. Legacy `collection` / `global` keys from\r\n * pre-0.6 rows are tolerated by `composeScopes` for one release; v0.7\r\n * drops the fallback (see CHANGELOG).\r\n */\r\ninterface ScopeRow {\r\n slug?: unknown\r\n /** @deprecated pre-0.6 collectionScopes shape — read via fallback only. */\r\n collection?: unknown\r\n /** @deprecated pre-0.6 globalScopes shape — read via fallback only. */\r\n global?: unknown\r\n actions?: unknown\r\n}\r\n\r\ninterface ApiKeyRow {\r\n id: string | number\r\n user: unknown\r\n preset?: ScopePreset | 'custom' | null\r\n collectionScopes?: ScopeRow[] | null\r\n globalScopes?: ScopeRow[] | null\r\n toolAllow?: string[] | null\r\n toolDeny?: string[] | null\r\n expiresAt?: string | Date | null\r\n revokedAt?: string | Date | null\r\n apiKey?: string | null\r\n keyPrefix?: string | null\r\n}\r\n\r\n/**\r\n * Reads the row's slug, tolerating the pre-0.6 `collection` / `global`\r\n * keys for one release. Logs a one-line warn when the legacy fallback\r\n * fires so operators can spot keys that need re-saving. The fallback is\r\n * scheduled for removal in v0.7.\r\n */\r\nlet warnedLegacyShape = false\r\nlet warnedLegacyNonCustomOverride = false\r\n\r\n/** @internal test-only: reset the one-time legacy warns. */\r\nexport function _resetLegacyWarnsForTests(): void {\r\n warnedLegacyShape = false\r\n warnedLegacyNonCustomOverride = false\r\n}\r\n\r\nfunction readScopeSlug(\r\n entry: ScopeRow,\r\n legacyKey: 'collection' | 'global',\r\n logger?: { warn?: (...args: unknown[]) => void } | undefined,\r\n): string | null {\r\n if (typeof entry?.slug === 'string') return entry.slug\r\n const legacy = entry?.[legacyKey]\r\n if (typeof legacy === 'string') {\r\n if (!warnedLegacyShape) {\r\n warnedLegacyShape = true\r\n logger?.warn?.(\r\n { event: 'mcp.auth.legacy_scope_shape', legacyKey },\r\n `[payload-mcp-toolkit] composeScopes read a pre-0.6 row using \\`${legacyKey}\\`; resave the API key to migrate to {slug, actions}. The fallback is removed in v0.7.`,\r\n )\r\n }\r\n return legacy\r\n }\r\n return null\r\n}\r\n\r\nconst VALID_ACTIONS: ReadonlySet<CollectionAction> = new Set(['read', 'create', 'update', 'delete'])\r\nconst VALID_GLOBAL_ACTIONS: ReadonlySet<GlobalAction> = new Set(['read', 'update'])\r\n\r\n/**\r\n * Builds the runtime `KeyScopes` shape consumed by `registry.assertScopeAllows`\r\n * from the typed scope fields on the api-key row.\r\n *\r\n * Returns null when no typed fields are populated AND no preset is set\r\n * (= full access — back-compat for pre-0.5 rows that pre-date scoped authz).\r\n *\r\n * Two complementary fail-closed rules:\r\n *\r\n * 1. **`'custom'` deny-all sentinel.** `'custom'` is a UI sentinel meaning\r\n * \"use my override fields\"; it never becomes `KeyScopes.preset` itself.\r\n * Payload persists unset JSON / select fields as `null`, so a fresh\r\n * Custom key with no overrides arrives as `{preset:'custom',\r\n * collectionScopes:null, globalScopes:null, toolAllow:null,\r\n * toolDeny:null}`. That row must deny everything (not fall through to\r\n * full access). The sentinel emits `{collections:{}, globals:{},\r\n * tools:{allow:[]}}`.\r\n *\r\n * 2. **Per-axis explicit-empty, Custom-only.** Under the Custom preset, an\r\n * empty array on any axis (even `[]`) is honoured as written — an empty\r\n * `toolAllow:[]` means \"deny all tools on this axis\", not \"no opinion\".\r\n * Under non-Custom presets, empty arrays are IGNORED because Payload's\r\n * hasMany / unpopulated-JSON reads return `[]` for fields the user\r\n * never touched (the override matrices are hidden in the admin UI under\r\n * non-Custom presets via `condition: isCustomPreset`). The on-write\r\n * counterpart of this rule lives in `createApiKeysCollection`'s\r\n * `beforeValidate` hook, which proactively nulls the override axes on\r\n * save when the preset is non-Custom — both layers must stay in sync.\r\n * Non-empty arrays still apply as layered narrowing under any preset.\r\n * `toolDeny` is a deny-list, so an empty array carries no entries — it\r\n * is dropped rather than emitted. NOTE: legacy non-Custom rows persisted\r\n * BEFORE v0.7.1 with populated stale override arrays continue to narrow\r\n * on read until each row is manually re-saved; the on-write fix only\r\n * applies to fresh writes.\r\n */\r\nexport function composeScopes(\r\n row: ApiKeyRow,\r\n logger?: { warn?: (...args: unknown[]) => void },\r\n): KeyScopes | null {\r\n const presetRaw = row.preset\r\n const hasPreset = typeof presetRaw === 'string' && presetRaw.length > 0\r\n const hasCollectionScopesField = Array.isArray(row.collectionScopes)\r\n const hasGlobalScopesField = Array.isArray(row.globalScopes)\r\n const hasToolAllowField = Array.isArray(row.toolAllow)\r\n const hasToolDenyField = Array.isArray(row.toolDeny)\r\n\r\n // Pre-0.5 back-compat: row has no preset and no typed scope fields at\r\n // all (all null/undefined). Treat as full access.\r\n if (\r\n !hasPreset &&\r\n !hasCollectionScopesField &&\r\n !hasGlobalScopesField &&\r\n !hasToolAllowField &&\r\n !hasToolDenyField\r\n ) {\r\n return null\r\n }\r\n\r\n // Sentinel: Custom preset with every axis null/undefined (no array\r\n // committed) denies everything. Payload-persisted unset JSON / select\r\n // fields arrive as null; this is the fresh-Custom-key case.\r\n if (\r\n presetRaw === 'custom' &&\r\n !hasCollectionScopesField &&\r\n !hasGlobalScopesField &&\r\n !hasToolAllowField &&\r\n !hasToolDenyField\r\n ) {\r\n return { collections: {}, globals: {}, tools: { allow: [] } }\r\n }\r\n\r\n const out: KeyScopes = {}\r\n const isCustomPreset = presetRaw === 'custom'\r\n if (hasPreset && !isCustomPreset) {\r\n out.preset = presetRaw as ScopePreset\r\n }\r\n\r\n // Under non-Custom presets, the override fields are hidden in the admin\r\n // UI (`condition: isCustomPreset`) and Payload's hasMany / relational\r\n // reads return `[]` for unpopulated relations even when the user never\r\n // touched them. Treating that `[]` as \"deny-all on this axis\" turns\r\n // every preset key into a deny-all key once it round-trips through the\r\n // DB. Apply the explicit-empty-means-deny semantic only when the user\r\n // is on the Custom preset (where the override fields are visible and\r\n // meaningful); under preset modes, ignore empty arrays so that the\r\n // preset alone drives access. Non-empty arrays still apply as layered\r\n // narrowing, matching the field description (\"layered on top of preset\r\n // and collection scopes\").\r\n const treatEmptyAsScope = isCustomPreset\r\n\r\n // Legacy-row warn: non-Custom preset rows persisted BEFORE v0.7.1 may\r\n // carry populated override arrays from a prior Custom configuration\r\n // (the on-write null-out hook landed in v0.7.1). These still apply as\r\n // layered narrowing on read — fail-closed-safe (narrows, never widens)\r\n // but may not match operator intent. Warn once per process so legacy\r\n // rows can be audited and re-saved.\r\n if (\r\n hasPreset &&\r\n !isCustomPreset &&\r\n !warnedLegacyNonCustomOverride &&\r\n ((hasCollectionScopesField && (row.collectionScopes as unknown[]).length > 0) ||\r\n (hasGlobalScopesField && (row.globalScopes as unknown[]).length > 0) ||\r\n (hasToolAllowField && (row.toolAllow as unknown[]).length > 0))\r\n ) {\r\n warnedLegacyNonCustomOverride = true\r\n logger?.warn?.(\r\n { event: 'mcp.auth.legacy_non_custom_override' },\r\n `[payload-mcp-toolkit] composeScopes read an API key with a non-Custom preset (${presetRaw}) ` +\r\n `carrying populated override arrays. These still narrow access as written, but the ` +\r\n `v0.7.1 admin UI clears overrides on preset switch — re-save affected keys to align ` +\r\n `persisted state with current admin semantics.`,\r\n )\r\n }\r\n\r\n const emitCollectionScopes =\r\n hasCollectionScopesField &&\r\n (treatEmptyAsScope || (row.collectionScopes as unknown[]).length > 0)\r\n if (emitCollectionScopes) {\r\n const collections: Record<string, CollectionAction[]> = {}\r\n for (const entry of row.collectionScopes as ScopeRow[]) {\r\n const slug = readScopeSlug(entry, 'collection', logger)\r\n if (!slug) continue\r\n const rawActions = Array.isArray(entry?.actions) ? entry.actions : []\r\n const actions = rawActions.filter(\r\n (a): a is CollectionAction => typeof a === 'string' && VALID_ACTIONS.has(a as CollectionAction),\r\n )\r\n collections[slug] = actions\r\n }\r\n out.collections = collections\r\n }\r\n\r\n const emitGlobalScopes =\r\n hasGlobalScopesField &&\r\n (treatEmptyAsScope || (row.globalScopes as unknown[]).length > 0)\r\n if (emitGlobalScopes) {\r\n const globals: Record<string, GlobalAction[]> = {}\r\n for (const entry of row.globalScopes as ScopeRow[]) {\r\n const slug = readScopeSlug(entry, 'global', logger)\r\n if (!slug) continue\r\n const rawActions = Array.isArray(entry?.actions) ? entry.actions : []\r\n const actions = rawActions.filter(\r\n (a): a is GlobalAction => typeof a === 'string' && VALID_GLOBAL_ACTIONS.has(a as GlobalAction),\r\n )\r\n globals[slug] = actions\r\n }\r\n out.globals = globals\r\n }\r\n\r\n const toolDeny = hasToolDenyField\r\n ? (row.toolDeny as unknown[]).filter((t): t is string => typeof t === 'string')\r\n : []\r\n const emitDeny = toolDeny.length > 0\r\n const emitToolAllow =\r\n hasToolAllowField &&\r\n (treatEmptyAsScope || (row.toolAllow as unknown[]).length > 0)\r\n\r\n if (emitToolAllow || emitDeny) {\r\n out.tools = {}\r\n if (emitToolAllow) {\r\n const toolAllow = (row.toolAllow as unknown[]).filter((t): t is string => typeof t === 'string')\r\n out.tools.allow = toolAllow\r\n }\r\n if (emitDeny) out.tools.deny = toolDeny\r\n }\r\n\r\n return out\r\n}\r\n\r\n/**\r\n * Builds the Payload `auth.strategies` entry that authenticates MCP requests.\r\n *\r\n * Authenticates `Authorization: Bearer <plaintext>` by computing the\r\n * upstream-compatible `apiKeyIndex` HMAC and looking up the row. On match,\r\n * it fires a non-blocking `lastUsedAt` write and hydrates `req.user` with\r\n * the linked user record + key context for downstream scope checks.\r\n */\r\nexport function createBearerStrategy(options: CreateBearerStrategyOptions): AuthStrategy {\r\n const { collectionSlug, userCollection } = options\r\n\r\n return {\r\n name: AUTH_STRATEGY_NAME,\r\n authenticate: async ({ headers, payload }) => {\r\n const headersAny = headers as unknown as\r\n | { get?: (name: string) => string | null }\r\n | Record<string, string | undefined>\r\n const headerValue =\r\n typeof (headersAny as { get?: (name: string) => string | null }).get === 'function'\r\n ? (headersAny as { get: (name: string) => string | null }).get('authorization')\r\n : (headersAny as Record<string, string | undefined>).authorization\r\n const token = extractBearerToken(headerValue ?? null)\r\n if (!token) return { user: null }\r\n\r\n const keyHash = hashKey(token, payload.secret)\r\n const where: Where = { apiKeyIndex: { equals: keyHash } }\r\n\r\n let docs: ApiKeyRow[] = []\r\n try {\r\n const result = (await payload.find({\r\n collection: collectionSlug,\r\n where,\r\n depth: 1,\r\n limit: 1,\r\n pagination: false,\r\n overrideAccess: true,\r\n })) as unknown as { docs: ApiKeyRow[] }\r\n docs = result.docs ?? []\r\n } catch (err) {\r\n payload.logger.error(\r\n { err, event: 'mcp.auth.lookup_failed' },\r\n '[payload-mcp-toolkit] API-key lookup failed',\r\n )\r\n return { user: null }\r\n }\r\n\r\n const row = docs[0]\r\n if (!row) return { user: null }\r\n\r\n const now = Date.now()\r\n if (row.revokedAt) return { user: null }\r\n if (row.expiresAt) {\r\n const expiry = new Date(row.expiresAt).getTime()\r\n if (Number.isFinite(expiry) && expiry < now) return { user: null }\r\n }\r\n\r\n const linkedUser = row.user\r\n if (!linkedUser || typeof linkedUser !== 'object') return { user: null }\r\n\r\n const effectiveScopes: KeyScopes | null = composeScopes(row, payload.logger)\r\n\r\n // Fire-and-forget: do not block the request on this write.\r\n void payload\r\n .update({\r\n collection: collectionSlug,\r\n id: row.id,\r\n data: { lastUsedAt: new Date().toISOString() } as Record<string, unknown>,\r\n overrideAccess: true,\r\n })\r\n .catch(() => {\r\n // Intentionally swallow: lastUsedAt drift is acceptable.\r\n })\r\n\r\n const user = linkedUser as Record<string, unknown>\r\n return {\r\n user: {\r\n ...user,\r\n collection: userCollection,\r\n _strategy: AUTH_STRATEGY_NAME,\r\n _mcpKey: {\r\n keyId: row.id,\r\n keyPrefix: typeof row.keyPrefix === 'string' ? row.keyPrefix : null,\r\n scopes: effectiveScopes,\r\n },\r\n } as unknown as PayloadRequest['user'],\r\n }\r\n },\r\n }\r\n}\r\n\r\n/**\r\n * Reads the per-request API-key context populated by the bearer strategy.\r\n * Returns null for non-MCP requests (e.g. cookie-authenticated admin users).\r\n */\r\nexport function getApiKeyContext(req: PayloadRequest): {\r\n keyId: string | number\r\n keyPrefix: string | null\r\n scopes: KeyScopes | null\r\n} | null {\r\n const user = req.user as\r\n | (Record<string, unknown> & {\r\n _mcpKey?: { keyId: string | number; keyPrefix: string | null; scopes: KeyScopes | null }\r\n })\r\n | null\r\n | undefined\r\n return user?._mcpKey ?? null\r\n}\r\n"],"names":["extractBearerToken","hashKey","AUTH_STRATEGY_NAME","warnedLegacyShape","warnedLegacyNonCustomOverride","_resetLegacyWarnsForTests","readScopeSlug","entry","legacyKey","logger","slug","legacy","warn","event","VALID_ACTIONS","Set","VALID_GLOBAL_ACTIONS","composeScopes","row","presetRaw","preset","hasPreset","length","hasCollectionScopesField","Array","isArray","collectionScopes","hasGlobalScopesField","globalScopes","hasToolAllowField","toolAllow","hasToolDenyField","toolDeny","collections","globals","tools","allow","out","isCustomPreset","treatEmptyAsScope","emitCollectionScopes","rawActions","actions","filter","a","has","emitGlobalScopes","t","emitDeny","emitToolAllow","deny","createBearerStrategy","options","collectionSlug","userCollection","name","authenticate","headers","payload","headersAny","headerValue","get","authorization","token","user","keyHash","secret","where","apiKeyIndex","equals","docs","result","find","collection","depth","limit","pagination","overrideAccess","err","error","now","Date","revokedAt","expiresAt","expiry","getTime","Number","isFinite","linkedUser","effectiveScopes","update","id","data","lastUsedAt","toISOString","catch","_strategy","_mcpKey","keyId","keyPrefix","scopes","getApiKeyContext","req"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/auth-strategy.ts"],"sourcesContent":["import type { AuthStrategy, PayloadRequest, Where } from 'payload'\nimport { extractBearerToken, hashKey } from './hash'\nimport type { CollectionAction, GlobalAction, KeyScopes, ScopePreset } from './types'\n\nexport type { CollectionAction, GlobalAction, KeyScopes, ScopePreset } from './types'\n\nexport const AUTH_STRATEGY_NAME = 'mcp-toolkit-bearer'\n\nexport interface CreateBearerStrategyOptions {\n /** Slug of the API-keys collection (defaults to `payload-mcp-api-keys`). */\n collectionSlug: string\n /** Slug of the user collection that API keys link to. */\n userCollection: string\n}\n\n/**\n * Stored shape for a row in `collectionScopes` / `globalScopes` (v0.6+).\n * The parent field name encodes the axis — collection-vs-global is not\n * repeated in the row payload. Legacy `collection` / `global` keys from\n * pre-0.6 rows are tolerated by `composeScopes` for one release; v0.7\n * drops the fallback (see CHANGELOG).\n */\ninterface ScopeRow {\n slug?: unknown\n /** @deprecated pre-0.6 collectionScopes shape — read via fallback only. */\n collection?: unknown\n /** @deprecated pre-0.6 globalScopes shape — read via fallback only. */\n global?: unknown\n actions?: unknown\n}\n\ninterface ApiKeyRow {\n id: string | number\n user: unknown\n preset?: ScopePreset | 'custom' | null\n collectionScopes?: ScopeRow[] | null\n globalScopes?: ScopeRow[] | null\n toolAllow?: string[] | null\n toolDeny?: string[] | null\n expiresAt?: string | Date | null\n revokedAt?: string | Date | null\n apiKey?: string | null\n keyPrefix?: string | null\n}\n\n/**\n * Reads the row's slug, tolerating the pre-0.6 `collection` / `global`\n * keys for one release. Logs a one-line warn when the legacy fallback\n * fires so operators can spot keys that need re-saving. The fallback is\n * scheduled for removal in v0.7.\n */\nlet warnedLegacyShape = false\nlet warnedLegacyNonCustomOverride = false\n\n/** @internal test-only: reset the one-time legacy warns. */\nexport function _resetLegacyWarnsForTests(): void {\n warnedLegacyShape = false\n warnedLegacyNonCustomOverride = false\n}\n\nfunction readScopeSlug(\n entry: ScopeRow,\n legacyKey: 'collection' | 'global',\n logger?: { warn?: (...args: unknown[]) => void } | undefined,\n): string | null {\n if (typeof entry?.slug === 'string') return entry.slug\n const legacy = entry?.[legacyKey]\n if (typeof legacy === 'string') {\n if (!warnedLegacyShape) {\n warnedLegacyShape = true\n logger?.warn?.(\n { event: 'mcp.auth.legacy_scope_shape', legacyKey },\n `[payload-mcp-toolkit] composeScopes read a pre-0.6 row using \\`${legacyKey}\\`; resave the API key to migrate to {slug, actions}. The fallback is removed in v0.7.`,\n )\n }\n return legacy\n }\n return null\n}\n\nconst VALID_ACTIONS: ReadonlySet<CollectionAction> = new Set(['read', 'create', 'update', 'delete'])\nconst VALID_GLOBAL_ACTIONS: ReadonlySet<GlobalAction> = new Set(['read', 'update'])\n\n/**\n * Builds the runtime `KeyScopes` shape consumed by `registry.assertScopeAllows`\n * from the typed scope fields on the api-key row.\n *\n * Returns null when no typed fields are populated AND no preset is set\n * (= full access — back-compat for pre-0.5 rows that pre-date scoped authz).\n *\n * Two complementary fail-closed rules:\n *\n * 1. **`'custom'` deny-all sentinel.** `'custom'` is a UI sentinel meaning\n * \"use my override fields\"; it never becomes `KeyScopes.preset` itself.\n * Payload persists unset JSON / select fields as `null`, so a fresh\n * Custom key with no overrides arrives as `{preset:'custom',\n * collectionScopes:null, globalScopes:null, toolAllow:null,\n * toolDeny:null}`. That row must deny everything (not fall through to\n * full access). The sentinel emits `{collections:{}, globals:{},\n * tools:{allow:[]}}`.\n *\n * 2. **Per-axis explicit-empty, Custom-only.** Under the Custom preset, an\n * empty array on any axis (even `[]`) is honoured as written — an empty\n * `toolAllow:[]` means \"deny all tools on this axis\", not \"no opinion\".\n * Under non-Custom presets, empty arrays are IGNORED because Payload's\n * hasMany / unpopulated-JSON reads return `[]` for fields the user\n * never touched (the override matrices are hidden in the admin UI under\n * non-Custom presets via `condition: isCustomPreset`). The on-write\n * counterpart of this rule lives in `createApiKeysCollection`'s\n * `beforeValidate` hook, which proactively nulls the override axes on\n * save when the preset is non-Custom — both layers must stay in sync.\n * Non-empty arrays still apply as layered narrowing under any preset.\n * `toolDeny` is a deny-list, so an empty array carries no entries — it\n * is dropped rather than emitted. NOTE: legacy non-Custom rows persisted\n * BEFORE v0.7.1 with populated stale override arrays continue to narrow\n * on read until each row is manually re-saved; the on-write fix only\n * applies to fresh writes.\n */\nexport function composeScopes(\n row: ApiKeyRow,\n logger?: { warn?: (...args: unknown[]) => void },\n): KeyScopes | null {\n const presetRaw = row.preset\n const hasPreset = typeof presetRaw === 'string' && presetRaw.length > 0\n const hasCollectionScopesField = Array.isArray(row.collectionScopes)\n const hasGlobalScopesField = Array.isArray(row.globalScopes)\n const hasToolAllowField = Array.isArray(row.toolAllow)\n const hasToolDenyField = Array.isArray(row.toolDeny)\n\n // Pre-0.5 back-compat: row has no preset and no typed scope fields at\n // all (all null/undefined). Treat as full access.\n if (\n !hasPreset &&\n !hasCollectionScopesField &&\n !hasGlobalScopesField &&\n !hasToolAllowField &&\n !hasToolDenyField\n ) {\n return null\n }\n\n // Sentinel: Custom preset with every axis null/undefined (no array\n // committed) denies everything. Payload-persisted unset JSON / select\n // fields arrive as null; this is the fresh-Custom-key case.\n if (\n presetRaw === 'custom' &&\n !hasCollectionScopesField &&\n !hasGlobalScopesField &&\n !hasToolAllowField &&\n !hasToolDenyField\n ) {\n return { collections: {}, globals: {}, tools: { allow: [] } }\n }\n\n const out: KeyScopes = {}\n const isCustomPreset = presetRaw === 'custom'\n if (hasPreset && !isCustomPreset) {\n out.preset = presetRaw as ScopePreset\n }\n\n // Under non-Custom presets, the override fields are hidden in the admin\n // UI (`condition: isCustomPreset`) and Payload's hasMany / relational\n // reads return `[]` for unpopulated relations even when the user never\n // touched them. Treating that `[]` as \"deny-all on this axis\" turns\n // every preset key into a deny-all key once it round-trips through the\n // DB. Apply the explicit-empty-means-deny semantic only when the user\n // is on the Custom preset (where the override fields are visible and\n // meaningful); under preset modes, ignore empty arrays so that the\n // preset alone drives access. Non-empty arrays still apply as layered\n // narrowing, matching the field description (\"layered on top of preset\n // and collection scopes\").\n const treatEmptyAsScope = isCustomPreset\n\n // Legacy-row warn: non-Custom preset rows persisted BEFORE v0.7.1 may\n // carry populated override arrays from a prior Custom configuration\n // (the on-write null-out hook landed in v0.7.1). These still apply as\n // layered narrowing on read — fail-closed-safe (narrows, never widens)\n // but may not match operator intent. Warn once per process so legacy\n // rows can be audited and re-saved.\n if (\n hasPreset &&\n !isCustomPreset &&\n !warnedLegacyNonCustomOverride &&\n ((hasCollectionScopesField && (row.collectionScopes as unknown[]).length > 0) ||\n (hasGlobalScopesField && (row.globalScopes as unknown[]).length > 0) ||\n (hasToolAllowField && (row.toolAllow as unknown[]).length > 0))\n ) {\n warnedLegacyNonCustomOverride = true\n logger?.warn?.(\n { event: 'mcp.auth.legacy_non_custom_override' },\n `[payload-mcp-toolkit] composeScopes read an API key with a non-Custom preset (${presetRaw}) ` +\n `carrying populated override arrays. These still narrow access as written, but the ` +\n `v0.7.1 admin UI clears overrides on preset switch — re-save affected keys to align ` +\n `persisted state with current admin semantics.`,\n )\n }\n\n const emitCollectionScopes =\n hasCollectionScopesField &&\n (treatEmptyAsScope || (row.collectionScopes as unknown[]).length > 0)\n if (emitCollectionScopes) {\n const collections: Record<string, CollectionAction[]> = {}\n for (const entry of row.collectionScopes as ScopeRow[]) {\n const slug = readScopeSlug(entry, 'collection', logger)\n if (!slug) continue\n const rawActions = Array.isArray(entry?.actions) ? entry.actions : []\n const actions = rawActions.filter(\n (a): a is CollectionAction => typeof a === 'string' && VALID_ACTIONS.has(a as CollectionAction),\n )\n collections[slug] = actions\n }\n out.collections = collections\n }\n\n const emitGlobalScopes =\n hasGlobalScopesField &&\n (treatEmptyAsScope || (row.globalScopes as unknown[]).length > 0)\n if (emitGlobalScopes) {\n const globals: Record<string, GlobalAction[]> = {}\n for (const entry of row.globalScopes as ScopeRow[]) {\n const slug = readScopeSlug(entry, 'global', logger)\n if (!slug) continue\n const rawActions = Array.isArray(entry?.actions) ? entry.actions : []\n const actions = rawActions.filter(\n (a): a is GlobalAction => typeof a === 'string' && VALID_GLOBAL_ACTIONS.has(a as GlobalAction),\n )\n globals[slug] = actions\n }\n out.globals = globals\n }\n\n const toolDeny = hasToolDenyField\n ? (row.toolDeny as unknown[]).filter((t): t is string => typeof t === 'string')\n : []\n const emitDeny = toolDeny.length > 0\n const emitToolAllow =\n hasToolAllowField &&\n (treatEmptyAsScope || (row.toolAllow as unknown[]).length > 0)\n\n if (emitToolAllow || emitDeny) {\n out.tools = {}\n if (emitToolAllow) {\n const toolAllow = (row.toolAllow as unknown[]).filter((t): t is string => typeof t === 'string')\n out.tools.allow = toolAllow\n }\n if (emitDeny) out.tools.deny = toolDeny\n }\n\n return out\n}\n\n/**\n * Builds the Payload `auth.strategies` entry that authenticates MCP requests.\n *\n * Authenticates `Authorization: Bearer <plaintext>` by computing the\n * upstream-compatible `apiKeyIndex` HMAC and looking up the row. On match,\n * it fires a non-blocking `lastUsedAt` write and hydrates `req.user` with\n * the linked user record + key context for downstream scope checks.\n */\nexport function createBearerStrategy(options: CreateBearerStrategyOptions): AuthStrategy {\n const { collectionSlug, userCollection } = options\n\n return {\n name: AUTH_STRATEGY_NAME,\n authenticate: async ({ headers, payload }) => {\n const headersAny = headers as unknown as\n | { get?: (name: string) => string | null }\n | Record<string, string | undefined>\n const headerValue =\n typeof (headersAny as { get?: (name: string) => string | null }).get === 'function'\n ? (headersAny as { get: (name: string) => string | null }).get('authorization')\n : (headersAny as Record<string, string | undefined>).authorization\n const token = extractBearerToken(headerValue ?? null)\n if (!token) return { user: null }\n\n const keyHash = hashKey(token, payload.secret)\n const where: Where = { apiKeyIndex: { equals: keyHash } }\n\n let docs: ApiKeyRow[] = []\n try {\n const result = (await payload.find({\n collection: collectionSlug,\n where,\n depth: 1,\n limit: 1,\n pagination: false,\n overrideAccess: true,\n })) as unknown as { docs: ApiKeyRow[] }\n docs = result.docs ?? []\n } catch (err) {\n payload.logger.error(\n { err, event: 'mcp.auth.lookup_failed' },\n '[payload-mcp-toolkit] API-key lookup failed',\n )\n return { user: null }\n }\n\n const row = docs[0]\n if (!row) return { user: null }\n\n const now = Date.now()\n if (row.revokedAt) return { user: null }\n if (row.expiresAt) {\n const expiry = new Date(row.expiresAt).getTime()\n if (Number.isFinite(expiry) && expiry < now) return { user: null }\n }\n\n const linkedUser = row.user\n if (!linkedUser || typeof linkedUser !== 'object') return { user: null }\n\n const effectiveScopes: KeyScopes | null = composeScopes(row, payload.logger)\n\n // Fire-and-forget: do not block the request on this write.\n void payload\n .update({\n collection: collectionSlug,\n id: row.id,\n data: { lastUsedAt: new Date().toISOString() } as Record<string, unknown>,\n overrideAccess: true,\n })\n .catch(() => {\n // Intentionally swallow: lastUsedAt drift is acceptable.\n })\n\n const user = linkedUser as Record<string, unknown>\n return {\n user: {\n ...user,\n collection: userCollection,\n _strategy: AUTH_STRATEGY_NAME,\n _mcpKey: {\n keyId: row.id,\n keyPrefix: typeof row.keyPrefix === 'string' ? row.keyPrefix : null,\n scopes: effectiveScopes,\n },\n } as unknown as PayloadRequest['user'],\n }\n },\n }\n}\n\n/**\n * Reads the per-request API-key context populated by the bearer strategy.\n * Returns null for non-MCP requests (e.g. cookie-authenticated admin users).\n */\nexport function getApiKeyContext(req: PayloadRequest): {\n keyId: string | number\n keyPrefix: string | null\n scopes: KeyScopes | null\n} | null {\n const user = req.user as\n | (Record<string, unknown> & {\n _mcpKey?: { keyId: string | number; keyPrefix: string | null; scopes: KeyScopes | null }\n })\n | null\n | undefined\n return user?._mcpKey ?? null\n}\n"],"names":["extractBearerToken","hashKey","AUTH_STRATEGY_NAME","warnedLegacyShape","warnedLegacyNonCustomOverride","_resetLegacyWarnsForTests","readScopeSlug","entry","legacyKey","logger","slug","legacy","warn","event","VALID_ACTIONS","Set","VALID_GLOBAL_ACTIONS","composeScopes","row","presetRaw","preset","hasPreset","length","hasCollectionScopesField","Array","isArray","collectionScopes","hasGlobalScopesField","globalScopes","hasToolAllowField","toolAllow","hasToolDenyField","toolDeny","collections","globals","tools","allow","out","isCustomPreset","treatEmptyAsScope","emitCollectionScopes","rawActions","actions","filter","a","has","emitGlobalScopes","t","emitDeny","emitToolAllow","deny","createBearerStrategy","options","collectionSlug","userCollection","name","authenticate","headers","payload","headersAny","headerValue","get","authorization","token","user","keyHash","secret","where","apiKeyIndex","equals","docs","result","find","collection","depth","limit","pagination","overrideAccess","err","error","now","Date","revokedAt","expiresAt","expiry","getTime","Number","isFinite","linkedUser","effectiveScopes","update","id","data","lastUsedAt","toISOString","catch","_strategy","_mcpKey","keyId","keyPrefix","scopes","getApiKeyContext","req"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import './oauth.css';
2
+ /** Compact sidebar prompt: one click copies the agent setup prompt. Shown only to accounts that may connect. */
3
+ export declare function AgentConnectPill(): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,121 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React, { useEffect, useState } from 'react';
4
+ import { toast, Tooltip, usePreferences, XIcon } from '@payloadcms/ui';
5
+ import { agentInstructions } from './agentInstructions';
6
+ import './oauth.css';
7
+ const PREFERENCE = 'mcp-agent-pill-dismissed';
8
+ // Browser navigation to this endpoint redirects to the admin setup page, whatever the admin route is.
9
+ const CONNECTIONS = '/api/mcp/oauth/connections';
10
+ // Marks from simple-icons (CC0).
11
+ const CLAUDE = 'm4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z';
12
+ const OPENAI = 'M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z';
13
+ function Mark({ path, color }) {
14
+ return /*#__PURE__*/ _jsx("svg", {
15
+ "aria-hidden": "true",
16
+ className: "mcp-agent-pill__mark",
17
+ style: color ? {
18
+ color
19
+ } : undefined,
20
+ viewBox: "0 0 24 24",
21
+ children: /*#__PURE__*/ _jsx("path", {
22
+ d: path,
23
+ fill: "currentColor"
24
+ })
25
+ });
26
+ }
27
+ /** Compact sidebar prompt: one click copies the agent setup prompt. Shown only to accounts that may connect. */ export function AgentConnectPill() {
28
+ const { getPreference, setPreference } = usePreferences();
29
+ const [setup, setSetup] = useState(null);
30
+ const [hover, setHover] = useState(false);
31
+ useEffect(()=>{
32
+ const controller = new AbortController();
33
+ async function load() {
34
+ try {
35
+ if (await getPreference(PREFERENCE)) return;
36
+ const response = await fetch(CONNECTIONS, {
37
+ headers: {
38
+ Accept: 'application/json'
39
+ },
40
+ credentials: 'same-origin',
41
+ cache: 'no-store',
42
+ signal: controller.signal
43
+ });
44
+ if (!response.ok) return;
45
+ const data = await response.json();
46
+ if (data.eligible) setSetup(data);
47
+ } catch {
48
+ // Optional prompt: stay hidden if it cannot load.
49
+ }
50
+ }
51
+ void load();
52
+ return ()=>controller.abort();
53
+ }, [
54
+ getPreference
55
+ ]);
56
+ if (!setup) return null;
57
+ const { resource, access } = setup;
58
+ async function copy() {
59
+ try {
60
+ await navigator.clipboard.writeText(agentInstructions(resource, access));
61
+ toast.success('Setup prompt copied. Paste it into Claude or ChatGPT.', {
62
+ action: {
63
+ label: 'Setup steps',
64
+ onClick: ()=>window.location.assign(CONNECTIONS)
65
+ }
66
+ });
67
+ } catch {
68
+ toast.error('Copy failed. Open AI connections to copy the prompt.', {
69
+ action: {
70
+ label: 'Open',
71
+ onClick: ()=>window.location.assign(CONNECTIONS)
72
+ }
73
+ });
74
+ }
75
+ }
76
+ function dismiss() {
77
+ setSetup(null);
78
+ void setPreference(PREFERENCE, true);
79
+ }
80
+ return /*#__PURE__*/ _jsxs("div", {
81
+ className: "mcp-agent-pill",
82
+ children: [
83
+ /*#__PURE__*/ _jsxs("button", {
84
+ className: "mcp-agent-pill__copy",
85
+ onBlur: ()=>setHover(false),
86
+ onClick: copy,
87
+ onFocus: ()=>setHover(true),
88
+ onMouseEnter: ()=>setHover(true),
89
+ onMouseLeave: ()=>setHover(false),
90
+ type: "button",
91
+ children: [
92
+ /*#__PURE__*/ _jsx("span", {
93
+ className: "mcp-agent-pill__label",
94
+ children: "Connect your AI agent"
95
+ }),
96
+ /*#__PURE__*/ _jsx(Mark, {
97
+ color: "#D97757",
98
+ path: CLAUDE
99
+ }),
100
+ /*#__PURE__*/ _jsx(Mark, {
101
+ path: OPENAI
102
+ }),
103
+ /*#__PURE__*/ _jsx(Tooltip, {
104
+ position: "top",
105
+ show: hover,
106
+ children: "Copies a setup prompt"
107
+ })
108
+ ]
109
+ }),
110
+ /*#__PURE__*/ _jsx("button", {
111
+ "aria-label": "Dismiss",
112
+ className: "mcp-agent-pill__dismiss",
113
+ onClick: dismiss,
114
+ type: "button",
115
+ children: /*#__PURE__*/ _jsx(XIcon, {})
116
+ })
117
+ ]
118
+ });
119
+ }
120
+
121
+ //# sourceMappingURL=AgentConnectPill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/AgentConnectPill.tsx"],"sourcesContent":["'use client'\n\nimport React, { useEffect, useState } from 'react'\nimport { toast, Tooltip, usePreferences, XIcon } from '@payloadcms/ui'\nimport { agentInstructions } from './agentInstructions'\nimport './oauth.css'\n\nconst PREFERENCE = 'mcp-agent-pill-dismissed'\n// Browser navigation to this endpoint redirects to the admin setup page, whatever the admin route is.\nconst CONNECTIONS = '/api/mcp/oauth/connections'\n// Marks from simple-icons (CC0).\nconst CLAUDE = 'm4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z'\nconst OPENAI = 'M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z'\n\ntype Setup = { resource: string; access: 'read-only' | 'editor'; eligible: boolean }\n\nfunction Mark({ path, color }: { path: string; color?: string }) {\n return <svg aria-hidden=\"true\" className=\"mcp-agent-pill__mark\" style={color ? { color } : undefined} viewBox=\"0 0 24 24\"><path d={path} fill=\"currentColor\" /></svg>\n}\n\n/** Compact sidebar prompt: one click copies the agent setup prompt. Shown only to accounts that may connect. */\nexport function AgentConnectPill() {\n const { getPreference, setPreference } = usePreferences()\n const [setup, setSetup] = useState<Setup | null>(null)\n const [hover, setHover] = useState(false)\n\n useEffect(() => {\n const controller = new AbortController()\n async function load() {\n try {\n if (await getPreference<boolean>(PREFERENCE)) return\n const response = await fetch(CONNECTIONS, {\n headers: { Accept: 'application/json' }, credentials: 'same-origin', cache: 'no-store', signal: controller.signal,\n })\n if (!response.ok) return\n const data = await response.json() as Setup\n if (data.eligible) setSetup(data)\n } catch {\n // Optional prompt: stay hidden if it cannot load.\n }\n }\n void load()\n return () => controller.abort()\n }, [getPreference])\n\n if (!setup) return null\n const { resource, access } = setup\n\n async function copy() {\n try {\n await navigator.clipboard.writeText(agentInstructions(resource, access))\n toast.success('Setup prompt copied. Paste it into Claude or ChatGPT.', {\n action: { label: 'Setup steps', onClick: () => window.location.assign(CONNECTIONS) },\n })\n } catch {\n toast.error('Copy failed. Open AI connections to copy the prompt.', {\n action: { label: 'Open', onClick: () => window.location.assign(CONNECTIONS) },\n })\n }\n }\n\n function dismiss() {\n setSetup(null)\n void setPreference(PREFERENCE, true)\n }\n\n return (\n <div className=\"mcp-agent-pill\">\n <button\n className=\"mcp-agent-pill__copy\"\n onBlur={() => setHover(false)}\n onClick={copy}\n onFocus={() => setHover(true)}\n onMouseEnter={() => setHover(true)}\n onMouseLeave={() => setHover(false)}\n type=\"button\"\n >\n <span className=\"mcp-agent-pill__label\">Connect your AI agent</span>\n <Mark color=\"#D97757\" path={CLAUDE} />\n <Mark path={OPENAI} />\n <Tooltip position=\"top\" show={hover}>Copies a setup prompt</Tooltip>\n </button>\n <button aria-label=\"Dismiss\" className=\"mcp-agent-pill__dismiss\" onClick={dismiss} type=\"button\">\n <XIcon />\n </button>\n </div>\n )\n}\n"],"names":["React","useEffect","useState","toast","Tooltip","usePreferences","XIcon","agentInstructions","PREFERENCE","CONNECTIONS","CLAUDE","OPENAI","Mark","path","color","svg","aria-hidden","className","style","undefined","viewBox","d","fill","AgentConnectPill","getPreference","setPreference","setup","setSetup","hover","setHover","controller","AbortController","load","response","fetch","headers","Accept","credentials","cache","signal","ok","data","json","eligible","abort","resource","access","copy","navigator","clipboard","writeText","success","action","label","onClick","window","location","assign","error","dismiss","div","button","onBlur","onFocus","onMouseEnter","onMouseLeave","type","span","position","show","aria-label"],"mappings":"AAAA;;AAEA,OAAOA,SAASC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAClD,SAASC,KAAK,EAAEC,OAAO,EAAEC,cAAc,EAAEC,KAAK,QAAQ,iBAAgB;AACtE,SAASC,iBAAiB,QAAQ,sBAAqB;AACvD,OAAO,cAAa;AAEpB,MAAMC,aAAa;AACnB,sGAAsG;AACtG,MAAMC,cAAc;AACpB,iCAAiC;AACjC,MAAMC,SAAS;AACf,MAAMC,SAAS;AAIf,SAASC,KAAK,EAAEC,IAAI,EAAEC,KAAK,EAAoC;IAC7D,qBAAO,KAACC;QAAIC,eAAY;QAAOC,WAAU;QAAuBC,OAAOJ,QAAQ;YAAEA;QAAM,IAAIK;QAAWC,SAAQ;kBAAY,cAAA,KAACP;YAAKQ,GAAGR;YAAMS,MAAK;;;AAChJ;AAEA,8GAA8G,GAC9G,OAAO,SAASC;IACd,MAAM,EAAEC,aAAa,EAAEC,aAAa,EAAE,GAAGpB;IACzC,MAAM,CAACqB,OAAOC,SAAS,GAAGzB,SAAuB;IACjD,MAAM,CAAC0B,OAAOC,SAAS,GAAG3B,SAAS;IAEnCD,UAAU;QACR,MAAM6B,aAAa,IAAIC;QACvB,eAAeC;YACb,IAAI;gBACF,IAAI,MAAMR,cAAuBhB,aAAa;gBAC9C,MAAMyB,WAAW,MAAMC,MAAMzB,aAAa;oBACxC0B,SAAS;wBAAEC,QAAQ;oBAAmB;oBAAGC,aAAa;oBAAeC,OAAO;oBAAYC,QAAQT,WAAWS,MAAM;gBACnH;gBACA,IAAI,CAACN,SAASO,EAAE,EAAE;gBAClB,MAAMC,OAAO,MAAMR,SAASS,IAAI;gBAChC,IAAID,KAAKE,QAAQ,EAAEhB,SAASc;YAC9B,EAAE,OAAM;YACN,kDAAkD;YACpD;QACF;QACA,KAAKT;QACL,OAAO,IAAMF,WAAWc,KAAK;IAC/B,GAAG;QAACpB;KAAc;IAElB,IAAI,CAACE,OAAO,OAAO;IACnB,MAAM,EAAEmB,QAAQ,EAAEC,MAAM,EAAE,GAAGpB;IAE7B,eAAeqB;QACb,IAAI;YACF,MAAMC,UAAUC,SAAS,CAACC,SAAS,CAAC3C,kBAAkBsC,UAAUC;YAChE3C,MAAMgD,OAAO,CAAC,yDAAyD;gBACrEC,QAAQ;oBAAEC,OAAO;oBAAeC,SAAS,IAAMC,OAAOC,QAAQ,CAACC,MAAM,CAAChD;gBAAa;YACrF;QACF,EAAE,OAAM;YACNN,MAAMuD,KAAK,CAAC,wDAAwD;gBAClEN,QAAQ;oBAAEC,OAAO;oBAAQC,SAAS,IAAMC,OAAOC,QAAQ,CAACC,MAAM,CAAChD;gBAAa;YAC9E;QACF;IACF;IAEA,SAASkD;QACPhC,SAAS;QACT,KAAKF,cAAcjB,YAAY;IACjC;IAEA,qBACE,MAACoD;QAAI3C,WAAU;;0BACb,MAAC4C;gBACC5C,WAAU;gBACV6C,QAAQ,IAAMjC,SAAS;gBACvByB,SAASP;gBACTgB,SAAS,IAAMlC,SAAS;gBACxBmC,cAAc,IAAMnC,SAAS;gBAC7BoC,cAAc,IAAMpC,SAAS;gBAC7BqC,MAAK;;kCAEL,KAACC;wBAAKlD,WAAU;kCAAwB;;kCACxC,KAACL;wBAAKE,OAAM;wBAAUD,MAAMH;;kCAC5B,KAACE;wBAAKC,MAAMF;;kCACZ,KAACP;wBAAQgE,UAAS;wBAAMC,MAAMzC;kCAAO;;;;0BAEvC,KAACiC;gBAAOS,cAAW;gBAAUrD,WAAU;gBAA0BqC,SAASK;gBAASO,MAAK;0BACtF,cAAA,KAAC5D;;;;AAIT"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/CollectionScopesMatrix.tsx"],"sourcesContent":["'use client'\r\n\r\nimport * as React from 'react'\r\nimport { ScopesTable } from './ScopesTable'\r\n\r\nconst ACTIONS = ['read', 'create', 'update', 'delete']\r\nconst ACTION_LABELS: Record<string, string> = {\r\n read: 'Read',\r\n create: 'Create',\r\n update: 'Update',\r\n delete: 'Delete',\r\n}\r\n\r\nexport interface CollectionScopesMatrixProps {\r\n path: string\r\n /** Forwarded via `clientProps` from `api-keys.ts`. */\r\n availableCollections?: string[]\r\n}\r\n\r\nfunction CollectionScopesMatrix(props: CollectionScopesMatrixProps): React.ReactElement {\r\n const items = Array.isArray(props.availableCollections) ? props.availableCollections : []\r\n return (\r\n <ScopesTable\r\n path={props.path}\r\n items={items}\r\n actions={ACTIONS}\r\n actionLabels={ACTION_LABELS}\r\n itemHeader=\"Collection\"\r\n title=\"Collection scopes\"\r\n description='Check the actions this key may perform on each collection. Unchecked rows are denied outright. Configured under the \"Custom\" preset; once set, these overrides apply to this key regardless of which preset is later selected.'\r\n emptyMessage=\"No collections are available to scope. Add collections to your Payload config and restart the dev server.\"\r\n />\r\n )\r\n}\r\n\r\nexport default CollectionScopesMatrix\r\n"],"names":["React","ScopesTable","ACTIONS","ACTION_LABELS","read","create","update","delete","CollectionScopesMatrix","props","items","Array","isArray","availableCollections","path","actions","actionLabels","itemHeader","title","description","emptyMessage"],"mappings":"AAAA;;AAEA,YAAYA,WAAW,QAAO;AAC9B,SAASC,WAAW,QAAQ,gBAAe;AAE3C,MAAMC,UAAU;IAAC;IAAQ;IAAU;IAAU;CAAS;AACtD,MAAMC,gBAAwC;IAC5CC,MAAM;IACNC,QAAQ;IACRC,QAAQ;IACRC,QAAQ;AACV;AAQA,SAASC,uBAAuBC,KAAkC;IAChE,MAAMC,QAAQC,MAAMC,OAAO,CAACH,MAAMI,oBAAoB,IAAIJ,MAAMI,oBAAoB,GAAG,EAAE;IACzF,qBACE,KAACZ;QACCa,MAAML,MAAMK,IAAI;QAChBJ,OAAOA;QACPK,SAASb;QACTc,cAAcb;QACdc,YAAW;QACXC,OAAM;QACNC,aAAY;QACZC,cAAa;;AAGnB;AAEA,eAAeZ,uBAAsB"}
1
+ {"version":3,"sources":["../../src/components/CollectionScopesMatrix.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { ScopesTable } from './ScopesTable'\n\nconst ACTIONS = ['read', 'create', 'update', 'delete']\nconst ACTION_LABELS: Record<string, string> = {\n read: 'Read',\n create: 'Create',\n update: 'Update',\n delete: 'Delete',\n}\n\nexport interface CollectionScopesMatrixProps {\n path: string\n /** Forwarded via `clientProps` from `api-keys.ts`. */\n availableCollections?: string[]\n}\n\nfunction CollectionScopesMatrix(props: CollectionScopesMatrixProps): React.ReactElement {\n const items = Array.isArray(props.availableCollections) ? props.availableCollections : []\n return (\n <ScopesTable\n path={props.path}\n items={items}\n actions={ACTIONS}\n actionLabels={ACTION_LABELS}\n itemHeader=\"Collection\"\n title=\"Collection scopes\"\n description='Check the actions this key may perform on each collection. Unchecked rows are denied outright. Configured under the \"Custom\" preset; once set, these overrides apply to this key regardless of which preset is later selected.'\n emptyMessage=\"No collections are available to scope. Add collections to your Payload config and restart the dev server.\"\n />\n )\n}\n\nexport default CollectionScopesMatrix\n"],"names":["React","ScopesTable","ACTIONS","ACTION_LABELS","read","create","update","delete","CollectionScopesMatrix","props","items","Array","isArray","availableCollections","path","actions","actionLabels","itemHeader","title","description","emptyMessage"],"mappings":"AAAA;;AAEA,YAAYA,WAAW,QAAO;AAC9B,SAASC,WAAW,QAAQ,gBAAe;AAE3C,MAAMC,UAAU;IAAC;IAAQ;IAAU;IAAU;CAAS;AACtD,MAAMC,gBAAwC;IAC5CC,MAAM;IACNC,QAAQ;IACRC,QAAQ;IACRC,QAAQ;AACV;AAQA,SAASC,uBAAuBC,KAAkC;IAChE,MAAMC,QAAQC,MAAMC,OAAO,CAACH,MAAMI,oBAAoB,IAAIJ,MAAMI,oBAAoB,GAAG,EAAE;IACzF,qBACE,KAACZ;QACCa,MAAML,MAAMK,IAAI;QAChBJ,OAAOA;QACPK,SAASb;QACTc,cAAcb;QACdc,YAAW;QACXC,OAAM;QACNC,aAAY;QACZC,cAAa;;AAGnB;AAEA,eAAeZ,uBAAsB"}
@@ -0,0 +1,10 @@
1
+ import { type Catalog, type Level } from '../oauth-permissions';
2
+ /**
3
+ * Consent-screen permission picker, capped at what the app asked for (`max`). Renders the hidden
4
+ * `permissions` field the approve form submits; the server re-checks every value.
5
+ */
6
+ export declare function ConsentPermissions({ catalog, max, onEmptyChange }: {
7
+ catalog: Catalog;
8
+ max: Level;
9
+ onEmptyChange: (empty: boolean) => void;
10
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,165 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import React, { useEffect, useState } from 'react';
4
+ import { CheckboxInput } from '@payloadcms/ui';
5
+ import { toWords } from 'payload/shared';
6
+ import { collectionActions, globalActions, summarize, toolsFor } from '../oauth-permissions';
7
+ import { ScopesMatrix } from './ScopesTable';
8
+ const ACTION_LABELS = {
9
+ read: 'Read',
10
+ create: 'Create',
11
+ update: 'Update'
12
+ };
13
+ const fullRows = (slugs, actions)=>slugs.map((slug)=>({
14
+ slug,
15
+ actions
16
+ }));
17
+ // Undefined when every row has every action: the form then sends nothing for that list, which means "all".
18
+ function toMap(rows, slugs, actions) {
19
+ const map = {};
20
+ for (const row of rows)if (typeof row.slug === 'string' && Array.isArray(row.actions)) map[row.slug] = row.actions;
21
+ return slugs.every((slug)=>actions.every((a)=>map[slug]?.includes(a))) ? undefined : map;
22
+ }
23
+ /**
24
+ * Consent-screen permission picker, capped at what the app asked for (`max`). Renders the hidden
25
+ * `permissions` field the approve form submits; the server re-checks every value.
26
+ */ export function ConsentPermissions({ catalog, max, onEmptyChange }) {
27
+ const [level, setLevel] = useState(max);
28
+ const [collections, setCollections] = useState(()=>fullRows(catalog.collections, collectionActions(max)));
29
+ const [globals, setGlobals] = useState(()=>fullRows(catalog.globals, globalActions(max)));
30
+ const [tools, setTools] = useState(()=>toolsFor(catalog, max).map((t)=>t.name));
31
+ function changeLevel(next) {
32
+ // A new level starts from everything it allows; picks made at the old level do not carry over.
33
+ setLevel(next);
34
+ setCollections(fullRows(catalog.collections, collectionActions(next)));
35
+ setGlobals(fullRows(catalog.globals, globalActions(next)));
36
+ setTools(toolsFor(catalog, next).map((t)=>t.name));
37
+ }
38
+ const offered = toolsFor(catalog, level);
39
+ const collectionMap = toMap(collections, catalog.collections, collectionActions(level));
40
+ const globalMap = toMap(globals, catalog.globals, globalActions(level));
41
+ const allTools = offered.every((t)=>tools.includes(t.name));
42
+ // The scope checker denies account-wide tools (search, upload) once collections or globals are limited.
43
+ const limited = Boolean(collectionMap || globalMap);
44
+ // A tool can run only if a ticked row allows its action (account-wide tools need nothing limited).
45
+ const reaches = (rows, action)=>rows.some((r)=>Array.isArray(r.actions) && r.actions.includes(action));
46
+ const usable = offered.filter((t)=>tools.includes(t.name) && (t.kind === 'account' ? !limited : reaches(t.kind === 'global' ? globals : collections, t.action)));
47
+ const empty = usable.length === 0;
48
+ useEffect(()=>onEmptyChange(empty), [
49
+ empty,
50
+ onEmptyChange
51
+ ]);
52
+ const choice = {
53
+ level,
54
+ ...collectionMap && {
55
+ collections: collectionMap
56
+ },
57
+ ...globalMap && {
58
+ globals: globalMap
59
+ },
60
+ ...!allTools && {
61
+ tools
62
+ }
63
+ };
64
+ const summary = summarize({
65
+ collections: collectionMap,
66
+ globals: globalMap,
67
+ ...!allTools && {
68
+ tools: {
69
+ allow: tools
70
+ }
71
+ }
72
+ }, level, catalog);
73
+ return /*#__PURE__*/ _jsxs("div", {
74
+ className: "mcp-oauth__permissions",
75
+ children: [
76
+ /*#__PURE__*/ _jsx("input", {
77
+ type: "hidden",
78
+ name: "permissions",
79
+ value: JSON.stringify(choice)
80
+ }),
81
+ max === 'editor' && /*#__PURE__*/ _jsx(CheckboxInput, {
82
+ checked: level === 'editor',
83
+ id: "mcp-oauth-level",
84
+ label: "Allow creating and updating entries",
85
+ onToggle: (e)=>changeLevel(e.currentTarget.checked ? 'editor' : 'read-only')
86
+ }),
87
+ /*#__PURE__*/ _jsxs("details", {
88
+ className: "mcp-oauth__customize",
89
+ children: [
90
+ /*#__PURE__*/ _jsxs("summary", {
91
+ children: [
92
+ /*#__PURE__*/ _jsx("strong", {
93
+ children: "Customize access"
94
+ }),
95
+ /*#__PURE__*/ _jsx("span", {
96
+ children: summary
97
+ })
98
+ ]
99
+ }),
100
+ /*#__PURE__*/ _jsxs("div", {
101
+ className: "mcp-oauth__customize-body",
102
+ children: [
103
+ /*#__PURE__*/ _jsx("h3", {
104
+ children: "Collections"
105
+ }),
106
+ /*#__PURE__*/ _jsx(ScopesMatrix, {
107
+ actionLabels: ACTION_LABELS,
108
+ actions: collectionActions(level),
109
+ id: "mcp-oauth-collections",
110
+ itemHeader: "Collection",
111
+ items: catalog.collections,
112
+ onChange: setCollections,
113
+ value: collections
114
+ }),
115
+ catalog.globals.length > 0 && /*#__PURE__*/ _jsxs(_Fragment, {
116
+ children: [
117
+ /*#__PURE__*/ _jsx("h3", {
118
+ children: "Global settings"
119
+ }),
120
+ /*#__PURE__*/ _jsx(ScopesMatrix, {
121
+ actionLabels: ACTION_LABELS,
122
+ actions: globalActions(level),
123
+ id: "mcp-oauth-globals",
124
+ itemHeader: "Global",
125
+ items: catalog.globals,
126
+ onChange: setGlobals,
127
+ value: globals
128
+ })
129
+ ]
130
+ }),
131
+ /*#__PURE__*/ _jsx("h3", {
132
+ children: "Tools"
133
+ }),
134
+ limited && /*#__PURE__*/ _jsx("p", {
135
+ children: "Search and upload tools need every collection and global, so they are off while access is limited."
136
+ }),
137
+ /*#__PURE__*/ _jsx("div", {
138
+ className: "mcp-oauth__tools",
139
+ children: offered.map((tool)=>/*#__PURE__*/ _jsx(CheckboxInput, {
140
+ checked: tools.includes(tool.name) && !(limited && tool.kind === 'account'),
141
+ id: `mcp-oauth-tool-${tool.name}`,
142
+ label: toWords(tool.name),
143
+ onToggle: (e)=>{
144
+ const on = e.currentTarget.checked;
145
+ setTools((current)=>on ? [
146
+ ...current,
147
+ tool.name
148
+ ] : current.filter((name)=>name !== tool.name));
149
+ },
150
+ readOnly: limited && tool.kind === 'account'
151
+ }, tool.name))
152
+ })
153
+ ]
154
+ })
155
+ ]
156
+ }),
157
+ empty && /*#__PURE__*/ _jsx("p", {
158
+ role: "alert",
159
+ children: "Pick at least one tool and one collection or global setting."
160
+ })
161
+ ]
162
+ });
163
+ }
164
+
165
+ //# sourceMappingURL=ConsentPermissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/ConsentPermissions.tsx"],"sourcesContent":["'use client'\n\nimport React, { useEffect, useState } from 'react'\nimport { CheckboxInput } from '@payloadcms/ui'\nimport { toWords } from 'payload/shared'\nimport { collectionActions, globalActions, summarize, toolsFor, type Catalog, type Choice, type Level } from '../oauth-permissions'\nimport { ScopesMatrix } from './ScopesTable'\n\ntype Rows = { slug?: unknown; actions?: unknown }[]\nconst ACTION_LABELS = { read: 'Read', create: 'Create', update: 'Update' }\n\nconst fullRows = (slugs: string[], actions: string[]): Rows => slugs.map(slug => ({ slug, actions }))\n\n// Undefined when every row has every action: the form then sends nothing for that list, which means \"all\".\nfunction toMap(rows: Rows, slugs: string[], actions: string[]): Record<string, string[]> | undefined {\n const map: Record<string, string[]> = {}\n for (const row of rows) if (typeof row.slug === 'string' && Array.isArray(row.actions)) map[row.slug] = row.actions as string[]\n return slugs.every(slug => actions.every(a => map[slug]?.includes(a))) ? undefined : map\n}\n\n/**\n * Consent-screen permission picker, capped at what the app asked for (`max`). Renders the hidden\n * `permissions` field the approve form submits; the server re-checks every value.\n */\nexport function ConsentPermissions({ catalog, max, onEmptyChange }: { catalog: Catalog; max: Level; onEmptyChange: (empty: boolean) => void }) {\n const [level, setLevel] = useState<Level>(max)\n const [collections, setCollections] = useState<Rows>(() => fullRows(catalog.collections, collectionActions(max)))\n const [globals, setGlobals] = useState<Rows>(() => fullRows(catalog.globals, globalActions(max)))\n const [tools, setTools] = useState<string[]>(() => toolsFor(catalog, max).map(t => t.name))\n\n function changeLevel(next: Level) {\n // A new level starts from everything it allows; picks made at the old level do not carry over.\n setLevel(next)\n setCollections(fullRows(catalog.collections, collectionActions(next)))\n setGlobals(fullRows(catalog.globals, globalActions(next)))\n setTools(toolsFor(catalog, next).map(t => t.name))\n }\n\n const offered = toolsFor(catalog, level)\n const collectionMap = toMap(collections, catalog.collections, collectionActions(level))\n const globalMap = toMap(globals, catalog.globals, globalActions(level))\n const allTools = offered.every(t => tools.includes(t.name))\n // The scope checker denies account-wide tools (search, upload) once collections or globals are limited.\n const limited = Boolean(collectionMap || globalMap)\n // A tool can run only if a ticked row allows its action (account-wide tools need nothing limited).\n const reaches = (rows: Rows, action: string) => rows.some(r => Array.isArray(r.actions) && r.actions.includes(action))\n const usable = offered.filter(t => tools.includes(t.name) &&\n (t.kind === 'account' ? !limited : reaches(t.kind === 'global' ? globals : collections, t.action)))\n const empty = usable.length === 0\n useEffect(() => onEmptyChange(empty), [empty, onEmptyChange])\n\n const choice: Choice = { level, ...(collectionMap && { collections: collectionMap }), ...(globalMap && { globals: globalMap }), ...(!allTools && { tools }) }\n const summary = summarize({ collections: collectionMap, globals: globalMap, ...(!allTools && { tools: { allow: tools } }) }, level, catalog)\n\n return (\n <div className=\"mcp-oauth__permissions\">\n <input type=\"hidden\" name=\"permissions\" value={JSON.stringify(choice)} />\n {max === 'editor' && (\n <CheckboxInput\n checked={level === 'editor'}\n id=\"mcp-oauth-level\"\n label=\"Allow creating and updating entries\"\n onToggle={e => changeLevel(e.currentTarget.checked ? 'editor' : 'read-only')}\n />\n )}\n <details className=\"mcp-oauth__customize\">\n <summary><strong>Customize access</strong><span>{summary}</span></summary>\n <div className=\"mcp-oauth__customize-body\">\n <h3>Collections</h3>\n <ScopesMatrix\n actionLabels={ACTION_LABELS}\n actions={collectionActions(level)}\n id=\"mcp-oauth-collections\"\n itemHeader=\"Collection\"\n items={catalog.collections}\n onChange={setCollections}\n value={collections}\n />\n {catalog.globals.length > 0 && (\n <>\n <h3>Global settings</h3>\n <ScopesMatrix\n actionLabels={ACTION_LABELS}\n actions={globalActions(level)}\n id=\"mcp-oauth-globals\"\n itemHeader=\"Global\"\n items={catalog.globals}\n onChange={setGlobals}\n value={globals}\n />\n </>\n )}\n <h3>Tools</h3>\n {limited && <p>Search and upload tools need every collection and global, so they are off while access is limited.</p>}\n <div className=\"mcp-oauth__tools\">\n {offered.map(tool => (\n <CheckboxInput\n checked={tools.includes(tool.name) && !(limited && tool.kind === 'account')}\n id={`mcp-oauth-tool-${tool.name}`}\n key={tool.name}\n label={toWords(tool.name)}\n onToggle={e => {\n const on = e.currentTarget.checked\n setTools(current => on ? [...current, tool.name] : current.filter(name => name !== tool.name))\n }}\n readOnly={limited && tool.kind === 'account'}\n />\n ))}\n </div>\n </div>\n </details>\n {empty && <p role=\"alert\">Pick at least one tool and one collection or global setting.</p>}\n </div>\n )\n}\n"],"names":["React","useEffect","useState","CheckboxInput","toWords","collectionActions","globalActions","summarize","toolsFor","ScopesMatrix","ACTION_LABELS","read","create","update","fullRows","slugs","actions","map","slug","toMap","rows","row","Array","isArray","every","a","includes","undefined","ConsentPermissions","catalog","max","onEmptyChange","level","setLevel","collections","setCollections","globals","setGlobals","tools","setTools","t","name","changeLevel","next","offered","collectionMap","globalMap","allTools","limited","Boolean","reaches","action","some","r","usable","filter","kind","empty","length","choice","summary","allow","div","className","input","type","value","JSON","stringify","checked","id","label","onToggle","e","currentTarget","details","strong","span","h3","actionLabels","itemHeader","items","onChange","p","tool","on","current","readOnly","role"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/GlobalScopesMatrix.tsx"],"sourcesContent":["'use client'\r\n\r\nimport * as React from 'react'\r\nimport { ScopesTable } from './ScopesTable'\r\n\r\nconst ACTIONS = ['read', 'update']\r\nconst ACTION_LABELS: Record<string, string> = {\r\n read: 'Read',\r\n update: 'Update',\r\n}\r\n\r\nexport interface GlobalScopesMatrixProps {\r\n path: string\r\n /** Forwarded via `clientProps` from `api-keys.ts`. */\r\n availableGlobals?: string[]\r\n}\r\n\r\nfunction GlobalScopesMatrix(props: GlobalScopesMatrixProps): React.ReactElement {\r\n const items = Array.isArray(props.availableGlobals) ? props.availableGlobals : []\r\n return (\r\n <ScopesTable\r\n path={props.path}\r\n items={items}\r\n actions={ACTIONS}\r\n actionLabels={ACTION_LABELS}\r\n itemHeader=\"Global\"\r\n title=\"Global scopes\"\r\n description='Check the actions this key may perform on each global. Globals only support Read and Update (singletons cannot be created or deleted). Configured under the \"Custom\" preset; once set, these overrides apply to this key regardless of which preset is later selected.'\r\n emptyMessage=\"No globals are available to scope. Add globals to your Payload config and restart the dev server.\"\r\n />\r\n )\r\n}\r\n\r\nexport default GlobalScopesMatrix\r\n"],"names":["React","ScopesTable","ACTIONS","ACTION_LABELS","read","update","GlobalScopesMatrix","props","items","Array","isArray","availableGlobals","path","actions","actionLabels","itemHeader","title","description","emptyMessage"],"mappings":"AAAA;;AAEA,YAAYA,WAAW,QAAO;AAC9B,SAASC,WAAW,QAAQ,gBAAe;AAE3C,MAAMC,UAAU;IAAC;IAAQ;CAAS;AAClC,MAAMC,gBAAwC;IAC5CC,MAAM;IACNC,QAAQ;AACV;AAQA,SAASC,mBAAmBC,KAA8B;IACxD,MAAMC,QAAQC,MAAMC,OAAO,CAACH,MAAMI,gBAAgB,IAAIJ,MAAMI,gBAAgB,GAAG,EAAE;IACjF,qBACE,KAACV;QACCW,MAAML,MAAMK,IAAI;QAChBJ,OAAOA;QACPK,SAASX;QACTY,cAAcX;QACdY,YAAW;QACXC,OAAM;QACNC,aAAY;QACZC,cAAa;;AAGnB;AAEA,eAAeZ,mBAAkB"}
1
+ {"version":3,"sources":["../../src/components/GlobalScopesMatrix.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { ScopesTable } from './ScopesTable'\n\nconst ACTIONS = ['read', 'update']\nconst ACTION_LABELS: Record<string, string> = {\n read: 'Read',\n update: 'Update',\n}\n\nexport interface GlobalScopesMatrixProps {\n path: string\n /** Forwarded via `clientProps` from `api-keys.ts`. */\n availableGlobals?: string[]\n}\n\nfunction GlobalScopesMatrix(props: GlobalScopesMatrixProps): React.ReactElement {\n const items = Array.isArray(props.availableGlobals) ? props.availableGlobals : []\n return (\n <ScopesTable\n path={props.path}\n items={items}\n actions={ACTIONS}\n actionLabels={ACTION_LABELS}\n itemHeader=\"Global\"\n title=\"Global scopes\"\n description='Check the actions this key may perform on each global. Globals only support Read and Update (singletons cannot be created or deleted). Configured under the \"Custom\" preset; once set, these overrides apply to this key regardless of which preset is later selected.'\n emptyMessage=\"No globals are available to scope. Add globals to your Payload config and restart the dev server.\"\n />\n )\n}\n\nexport default GlobalScopesMatrix\n"],"names":["React","ScopesTable","ACTIONS","ACTION_LABELS","read","update","GlobalScopesMatrix","props","items","Array","isArray","availableGlobals","path","actions","actionLabels","itemHeader","title","description","emptyMessage"],"mappings":"AAAA;;AAEA,YAAYA,WAAW,QAAO;AAC9B,SAASC,WAAW,QAAQ,gBAAe;AAE3C,MAAMC,UAAU;IAAC;IAAQ;CAAS;AAClC,MAAMC,gBAAwC;IAC5CC,MAAM;IACNC,QAAQ;AACV;AAQA,SAASC,mBAAmBC,KAA8B;IACxD,MAAMC,QAAQC,MAAMC,OAAO,CAACH,MAAMI,gBAAgB,IAAIJ,MAAMI,gBAAgB,GAAG,EAAE;IACjF,qBACE,KAACV;QACCW,MAAML,MAAMK,IAAI;QAChBJ,OAAOA;QACPK,SAASX;QACTY,cAAcX;QACdY,YAAW;QACXC,OAAM;QACNC,aAAY;QACZC,cAAa;;AAGnB;AAEA,eAAeZ,mBAAkB"}
@@ -0,0 +1,4 @@
1
+ import './oauth.css';
2
+ export declare function OAuthView({ mode }: {
3
+ mode: 'authorize' | 'connections';
4
+ }): import("react/jsx-runtime").JSX.Element;