payload-mcp-toolkit 0.3.1 → 0.3.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.
- package/README.md +150 -150
- package/dist/__tests__/introspection.test.js.map +1 -1
- package/dist/draft-workflow.d.ts +5 -4
- package/dist/draft-workflow.js +36 -28
- package/dist/draft-workflow.js.map +1 -1
- package/dist/index.js +14 -14
- package/dist/index.js.map +1 -1
- package/dist/introspection.js +33 -33
- package/dist/introspection.js.map +1 -1
- package/dist/prompts.js +5 -5
- package/dist/prompts.js.map +1 -1
- package/dist/resources.js +8 -8
- package/dist/resources.js.map +1 -1
- package/dist/types.js +5 -5
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,150 +1,150 @@
|
|
|
1
|
-
# payload-mcp-toolkit
|
|
2
|
-
|
|
3
|
-
> Schema-aware MCP toolkit for Payload CMS — wraps the official [`@payloadcms/plugin-mcp`](https://github.com/payloadcms/payload/tree/main/packages/plugin-mcp) with introspected prompts, resources, draft workflow, and AI-friendly tools so non-technical editors can manage content via AI chat.
|
|
4
|
-
|
|
5
|
-
## What it does
|
|
6
|
-
|
|
7
|
-
The official Payload MCP plugin gives every collection a generic CRUD surface. That works, but an LLM driving it has no idea:
|
|
8
|
-
|
|
9
|
-
- which collections support drafts vs publish-immediately,
|
|
10
|
-
- which block types are valid inside which sections,
|
|
11
|
-
- which fields are searchable for resolving relationships,
|
|
12
|
-
- how to compose a page layout without trial and error.
|
|
13
|
-
|
|
14
|
-
`payload-mcp-toolkit` introspects the Payload config at boot, then layers schema-aware **prompts**, **resources**, and **tools** on top of the official plugin so an AI client (Claude Desktop, Claude API, any MCP-compatible chat) can drive your CMS confidently.
|
|
15
|
-
|
|
16
|
-
## What it adds
|
|
17
|
-
|
|
18
|
-
**Auto-generated prompts** (no setup required):
|
|
19
|
-
- `contentModelOverview` — every collection, fields, and relationships.
|
|
20
|
-
- `blockCompositionGuide` — section/leaf hierarchy and nesting rules.
|
|
21
|
-
- `draftWorkflowGuide` — which collections need `publishDraft` to go live.
|
|
22
|
-
|
|
23
|
-
**Auto-generated resources** (machine-readable JSON for the LLM):
|
|
24
|
-
- `blocks://catalog`, `collections://schema`, `collections://relationships`.
|
|
25
|
-
|
|
26
|
-
**Custom tools (10, plus an auto-registered scheduler)**
|
|
27
|
-
|
|
28
|
-
*Authoring*
|
|
29
|
-
- `patchLayout` — surgical append/prepend/insertAt/replaceAt against any blocks-typed field. Validates each block (recursively, at any depth) against the introspected nesting map. Safer than `updateDocument` for incremental layout edits.
|
|
30
|
-
- `updateDocument` — Local-API based update that survives the upload-field bug in the official plugin.
|
|
31
|
-
- `uploadMedia` — fetch a public HTTPS image, validate (SSRF-safe with streaming size cap), create a Media doc.
|
|
32
|
-
|
|
33
|
-
*Discovery*
|
|
34
|
-
- `resolveReference` — search collections by name/title/slug for relationship IDs.
|
|
35
|
-
- `searchContent` — natural-language editor triage. Filter by `status`, `olderThanDays` / `newerThanDays`, `missingFields`, free-text `query`, scoped to one collection or all.
|
|
36
|
-
|
|
37
|
-
*Lifecycle / safety*
|
|
38
|
-
- `publishDraft` — flip `_status` from draft to published.
|
|
39
|
-
- `schedulePublish` — **bring your own scheduler.** Stamps a future `publishedAt` on a draft and leaves `_status: 'draft'`; it does **not** itself flip status at the appointed time. Auto-registered only for collections that have both drafts AND a `publishedAt` date field. Wire up a [Payload Jobs Queue task](https://payloadcms.com/docs/jobs-queue/scheduled-jobs), external cron, or `beforeRead` hook to actually publish on schedule.
|
|
40
|
-
- `listVersions` — recent saved versions of a draft document.
|
|
41
|
-
- `restoreVersion` — roll a document back to a saved version (creates a new version on top, so reversible).
|
|
42
|
-
- `safeDelete` — relationship-aware delete. Walks the relationship graph, refuses with a structured impact summary if other documents reference the target. Fail-closed on permission errors. Override with `confirm: true`.
|
|
43
|
-
|
|
44
|
-
**Draft workflow** wired into the official plugin's `mcpCollections`:
|
|
45
|
-
-
|
|
46
|
-
- Appends preview URLs to draft responses by calling each collection's own `admin.livePreview.url` or `admin.preview` function — no separate path config needed.
|
|
47
|
-
|
|
48
|
-
## Install
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
pnpm add payload-mcp-toolkit @payloadcms/plugin-mcp
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Peer dependencies: `payload` ^3, `@payloadcms/plugin-mcp` ^3, `zod` ^3.
|
|
55
|
-
|
|
56
|
-
## Use — zero config
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
// payload.config.ts
|
|
60
|
-
import { contentToolkitPlugin } from 'payload-mcp-toolkit'
|
|
61
|
-
|
|
62
|
-
export default buildConfig({
|
|
63
|
-
// ...your collections, blocks, globals
|
|
64
|
-
serverURL: process.env.SITE_URL, // used for absolute preview URLs
|
|
65
|
-
admin: { user: 'users' }, // your auth collection
|
|
66
|
-
plugins: [contentToolkitPlugin()],
|
|
67
|
-
})
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
That's it. The toolkit infers everything from your Payload config:
|
|
71
|
-
- **Draft behavior** — collections with `versions.drafts` get `always-draft` (
|
|
72
|
-
- **Preview URLs** — pulled from each collection's `admin.livePreview.url` (or `admin.preview` as a fallback). If neither is set, draft responses just get a generic admin-panel hint.
|
|
73
|
-
- **Block nesting** — for every blocks-typed field, anywhere in the schema, the toolkit records which slugs are allowed. The AI composes layouts at any depth from that map.
|
|
74
|
-
- **Auth collection** — comes from `admin.user` (the standard Payload setting). The official plugin handles this directly.
|
|
75
|
-
|
|
76
|
-
## Optional configuration
|
|
77
|
-
|
|
78
|
-
Every option is an escape hatch — pass only what you need:
|
|
79
|
-
|
|
80
|
-
```ts
|
|
81
|
-
contentToolkitPlugin({
|
|
82
|
-
preview: {
|
|
83
|
-
siteUrl: 'https://staging.example.com', // override serverURL
|
|
84
|
-
disabled: false, // set true to suppress preview URLs entirely
|
|
85
|
-
},
|
|
86
|
-
draftBehavior: {
|
|
87
|
-
posts: 'always-publish', //
|
|
88
|
-
},
|
|
89
|
-
userCollection: 'admins', // override admin.user
|
|
90
|
-
exclude: {
|
|
91
|
-
collections: ['internal-bookkeeping'],
|
|
92
|
-
globals: ['secret-config'],
|
|
93
|
-
},
|
|
94
|
-
mediaUpload: {
|
|
95
|
-
maxFileSize: 25 * 1024 * 1024,
|
|
96
|
-
collectionSlug: 'images',
|
|
97
|
-
},
|
|
98
|
-
domainPrompts: [
|
|
99
|
-
{
|
|
100
|
-
name: 'siteVocabulary',
|
|
101
|
-
title: 'Site Vocabulary',
|
|
102
|
-
description: 'Site-specific terms the AI should know.',
|
|
103
|
-
content: '...',
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
})
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
| Option | Description |
|
|
110
|
-
|---|---|
|
|
111
|
-
| `preview.siteUrl` | Base URL for preview links. Defaults to `serverURL`, then `NEXT_PUBLIC_SERVER_URL`/`SITE_URL` env vars. |
|
|
112
|
-
| `preview.disabled` | Suppress preview URL injection on draft responses. |
|
|
113
|
-
| `draftBehavior` | Per-collection override of inferred behavior. |
|
|
114
|
-
| `userCollection` | Override `admin.user` for API key linkage. |
|
|
115
|
-
| `exclude.collections` / `exclude.globals` | Hide from MCP exposure. |
|
|
116
|
-
| `domainPrompts` | Site-specific vocabulary prompts. |
|
|
117
|
-
| `mediaUpload.maxFileSize` | Default 10MB. Enforced as a streaming cap, not a post-buffer check. |
|
|
118
|
-
| `mediaUpload.collectionSlug` | Default `'media'`. |
|
|
119
|
-
|
|
120
|
-
## Development
|
|
121
|
-
|
|
122
|
-
This package follows the [official Payload 3 plugin template](https://github.com/payloadcms/payload/tree/main/templates/plugin) layout: source in `src/`, a fully-working Payload + Next.js app in `dev/`, source-export `package.json` so the dev harness consumes the plugin directly without a build step.
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
pnpm install
|
|
126
|
-
cp dev/.env.example dev/.env
|
|
127
|
-
pnpm dev # boot dev/ Next.js + Payload at http://localhost:3000
|
|
128
|
-
pnpm test # vitest — runs introspection unit tests
|
|
129
|
-
pnpm build # produce dist/ for npm publish
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
The dev harness ships with a realistic CMS schema:
|
|
133
|
-
- `Pages` — block-based layout (FullWidth, TwoColumn, CtaBanner, HeadingOnly), drafts enabled.
|
|
134
|
-
- `Posts` — title/slug/excerpt/content/cover/category/authors/tags/SEO, drafts enabled.
|
|
135
|
-
- `Authors`, `Categories`, `Media`, `Users` — taxonomy + auth.
|
|
136
|
-
- `SiteSettings` — global with site name, logo, social, footer.
|
|
137
|
-
- 5 leaf blocks (Heading, RichText, Image, ButtonGroup, Quote) and 4 section blocks.
|
|
138
|
-
|
|
139
|
-
Seed sample content:
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
# Generate the admin import map first time:
|
|
143
|
-
pnpm dev:generate-importmap
|
|
144
|
-
|
|
145
|
-
# Then visit http://localhost:3000/admin and create your first user.
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## License
|
|
149
|
-
|
|
150
|
-
MIT
|
|
1
|
+
# payload-mcp-toolkit
|
|
2
|
+
|
|
3
|
+
> Schema-aware MCP toolkit for Payload CMS — wraps the official [`@payloadcms/plugin-mcp`](https://github.com/payloadcms/payload/tree/main/packages/plugin-mcp) with introspected prompts, resources, draft workflow, and AI-friendly tools so non-technical editors can manage content via AI chat.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
The official Payload MCP plugin gives every collection a generic CRUD surface. That works, but an LLM driving it has no idea:
|
|
8
|
+
|
|
9
|
+
- which collections support drafts vs publish-immediately,
|
|
10
|
+
- which block types are valid inside which sections,
|
|
11
|
+
- which fields are searchable for resolving relationships,
|
|
12
|
+
- how to compose a page layout without trial and error.
|
|
13
|
+
|
|
14
|
+
`payload-mcp-toolkit` introspects the Payload config at boot, then layers schema-aware **prompts**, **resources**, and **tools** on top of the official plugin so an AI client (Claude Desktop, Claude API, any MCP-compatible chat) can drive your CMS confidently.
|
|
15
|
+
|
|
16
|
+
## What it adds
|
|
17
|
+
|
|
18
|
+
**Auto-generated prompts** (no setup required):
|
|
19
|
+
- `contentModelOverview` — every collection, fields, and relationships.
|
|
20
|
+
- `blockCompositionGuide` — section/leaf hierarchy and nesting rules.
|
|
21
|
+
- `draftWorkflowGuide` — which collections need `publishDraft` to go live.
|
|
22
|
+
|
|
23
|
+
**Auto-generated resources** (machine-readable JSON for the LLM):
|
|
24
|
+
- `blocks://catalog`, `collections://schema`, `collections://relationships`.
|
|
25
|
+
|
|
26
|
+
**Custom tools (10, plus an auto-registered scheduler)**
|
|
27
|
+
|
|
28
|
+
*Authoring*
|
|
29
|
+
- `patchLayout` — surgical append/prepend/insertAt/replaceAt against any blocks-typed field. Validates each block (recursively, at any depth) against the introspected nesting map. Safer than `updateDocument` for incremental layout edits.
|
|
30
|
+
- `updateDocument` — Local-API based update that survives the upload-field bug in the official plugin.
|
|
31
|
+
- `uploadMedia` — fetch a public HTTPS image, validate (SSRF-safe with streaming size cap), create a Media doc.
|
|
32
|
+
|
|
33
|
+
*Discovery*
|
|
34
|
+
- `resolveReference` — search collections by name/title/slug for relationship IDs.
|
|
35
|
+
- `searchContent` — natural-language editor triage. Filter by `status`, `olderThanDays` / `newerThanDays`, `missingFields`, free-text `query`, scoped to one collection or all.
|
|
36
|
+
|
|
37
|
+
*Lifecycle / safety*
|
|
38
|
+
- `publishDraft` — flip `_status` from draft to published.
|
|
39
|
+
- `schedulePublish` — **bring your own scheduler.** Stamps a future `publishedAt` on a draft and leaves `_status: 'draft'`; it does **not** itself flip status at the appointed time. Auto-registered only for collections that have both drafts AND a `publishedAt` date field. Wire up a [Payload Jobs Queue task](https://payloadcms.com/docs/jobs-queue/scheduled-jobs), external cron, or `beforeRead` hook to actually publish on schedule.
|
|
40
|
+
- `listVersions` — recent saved versions of a draft document.
|
|
41
|
+
- `restoreVersion` — roll a document back to a saved version (creates a new version on top, so reversible).
|
|
42
|
+
- `safeDelete` — relationship-aware delete. Walks the relationship graph, refuses with a structured impact summary if other documents reference the target. Fail-closed on permission errors. Override with `confirm: true`.
|
|
43
|
+
|
|
44
|
+
**Draft workflow** wired into the official plugin's `mcpCollections`:
|
|
45
|
+
- The official plugin's per-collection raw `update<Resource>` tool is disabled for every collection. Updates flow through `updateDocument` / `patchLayout` (local-API based), which preserve draft semantics for draftable collections and survive the upload-field / schema-conversion bugs in the official plugin's update path.
|
|
46
|
+
- Appends preview URLs to draft responses by calling each collection's own `admin.livePreview.url` or `admin.preview` function — no separate path config needed.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pnpm add payload-mcp-toolkit @payloadcms/plugin-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Peer dependencies: `payload` ^3, `@payloadcms/plugin-mcp` ^3, `zod` ^3.
|
|
55
|
+
|
|
56
|
+
## Use — zero config
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// payload.config.ts
|
|
60
|
+
import { contentToolkitPlugin } from 'payload-mcp-toolkit'
|
|
61
|
+
|
|
62
|
+
export default buildConfig({
|
|
63
|
+
// ...your collections, blocks, globals
|
|
64
|
+
serverURL: process.env.SITE_URL, // used for absolute preview URLs
|
|
65
|
+
admin: { user: 'users' }, // your auth collection
|
|
66
|
+
plugins: [contentToolkitPlugin()],
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
That's it. The toolkit infers everything from your Payload config:
|
|
71
|
+
- **Draft behavior** — collections with `versions.drafts` get `always-draft` semantics (clients flow through `publishDraft` / `patchLayout` / `updateDocument`); others publish immediately. The official plugin's raw `update<Resource>` tool is disabled across the board — `updateDocument` replaces it.
|
|
72
|
+
- **Preview URLs** — pulled from each collection's `admin.livePreview.url` (or `admin.preview` as a fallback). If neither is set, draft responses just get a generic admin-panel hint.
|
|
73
|
+
- **Block nesting** — for every blocks-typed field, anywhere in the schema, the toolkit records which slugs are allowed. The AI composes layouts at any depth from that map.
|
|
74
|
+
- **Auth collection** — comes from `admin.user` (the standard Payload setting). The official plugin handles this directly.
|
|
75
|
+
|
|
76
|
+
## Optional configuration
|
|
77
|
+
|
|
78
|
+
Every option is an escape hatch — pass only what you need:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
contentToolkitPlugin({
|
|
82
|
+
preview: {
|
|
83
|
+
siteUrl: 'https://staging.example.com', // override serverURL
|
|
84
|
+
disabled: false, // set true to suppress preview URLs entirely
|
|
85
|
+
},
|
|
86
|
+
draftBehavior: {
|
|
87
|
+
posts: 'always-publish', // publish immediately on update instead of saving a draft
|
|
88
|
+
},
|
|
89
|
+
userCollection: 'admins', // override admin.user
|
|
90
|
+
exclude: {
|
|
91
|
+
collections: ['internal-bookkeeping'],
|
|
92
|
+
globals: ['secret-config'],
|
|
93
|
+
},
|
|
94
|
+
mediaUpload: {
|
|
95
|
+
maxFileSize: 25 * 1024 * 1024,
|
|
96
|
+
collectionSlug: 'images',
|
|
97
|
+
},
|
|
98
|
+
domainPrompts: [
|
|
99
|
+
{
|
|
100
|
+
name: 'siteVocabulary',
|
|
101
|
+
title: 'Site Vocabulary',
|
|
102
|
+
description: 'Site-specific terms the AI should know.',
|
|
103
|
+
content: '...',
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| Option | Description |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `preview.siteUrl` | Base URL for preview links. Defaults to `serverURL`, then `NEXT_PUBLIC_SERVER_URL`/`SITE_URL` env vars. |
|
|
112
|
+
| `preview.disabled` | Suppress preview URL injection on draft responses. |
|
|
113
|
+
| `draftBehavior` | Per-collection override of inferred behavior. |
|
|
114
|
+
| `userCollection` | Override `admin.user` for API key linkage. |
|
|
115
|
+
| `exclude.collections` / `exclude.globals` | Hide from MCP exposure. |
|
|
116
|
+
| `domainPrompts` | Site-specific vocabulary prompts. |
|
|
117
|
+
| `mediaUpload.maxFileSize` | Default 10MB. Enforced as a streaming cap, not a post-buffer check. |
|
|
118
|
+
| `mediaUpload.collectionSlug` | Default `'media'`. |
|
|
119
|
+
|
|
120
|
+
## Development
|
|
121
|
+
|
|
122
|
+
This package follows the [official Payload 3 plugin template](https://github.com/payloadcms/payload/tree/main/templates/plugin) layout: source in `src/`, a fully-working Payload + Next.js app in `dev/`, source-export `package.json` so the dev harness consumes the plugin directly without a build step.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pnpm install
|
|
126
|
+
cp dev/.env.example dev/.env
|
|
127
|
+
pnpm dev # boot dev/ Next.js + Payload at http://localhost:3000
|
|
128
|
+
pnpm test # vitest — runs introspection unit tests
|
|
129
|
+
pnpm build # produce dist/ for npm publish
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The dev harness ships with a realistic CMS schema:
|
|
133
|
+
- `Pages` — block-based layout (FullWidth, TwoColumn, CtaBanner, HeadingOnly), drafts enabled.
|
|
134
|
+
- `Posts` — title/slug/excerpt/content/cover/category/authors/tags/SEO, drafts enabled.
|
|
135
|
+
- `Authors`, `Categories`, `Media`, `Users` — taxonomy + auth.
|
|
136
|
+
- `SiteSettings` — global with site name, logo, social, footer.
|
|
137
|
+
- 5 leaf blocks (Heading, RichText, Image, ButtonGroup, Quote) and 4 section blocks.
|
|
138
|
+
|
|
139
|
+
Seed sample content:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Generate the admin import map first time:
|
|
143
|
+
pnpm dev:generate-importmap
|
|
144
|
+
|
|
145
|
+
# Then visit http://localhost:3000/admin and create your first user.
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/__tests__/introspection.test.ts"],"sourcesContent":["import { describe, it, expect } from 'vitest'\r\nimport type { Block, CollectionConfig } from 'payload'\r\nimport {\r\n introspectCollection,\r\n introspectCollections,\r\n introspectBlocks,\r\n buildBlockNestingMap,\r\n buildRelationshipGraph,\r\n} from '../introspection'\r\n\r\n// ─── Sample schema (kept inline so the test is self-contained) ─────\r\n\r\nconst Media: CollectionConfig = {\r\n slug: 'media',\r\n upload: true,\r\n fields: [{ name: 'alt', type: 'text', required: true }],\r\n}\r\n\r\nconst Categories: CollectionConfig = {\r\n slug: 'categories',\r\n fields: [\r\n { name: 'name', type: 'text', required: true },\r\n { name: 'slug', type: 'text', required: true },\r\n ],\r\n}\r\n\r\nconst Authors: CollectionConfig = {\r\n slug: 'authors',\r\n fields: [\r\n { name: 'name', type: 'text', required: true },\r\n { name: 'slug', type: 'text', required: true },\r\n { name: 'avatar', type: 'upload', relationTo: 'media' },\r\n ],\r\n}\r\n\r\n// Leaf-style blocks\r\nconst Heading: Block = {\r\n slug: 'heading',\r\n fields: [\r\n { name: 'text', type: 'text', required: true },\r\n {\r\n name: 'level',\r\n type: 'select',\r\n options: ['h1', 'h2', 'h3'],\r\n defaultValue: 'h2',\r\n },\r\n {\r\n name: 'align',\r\n type: 'select',\r\n options: ['left', 'center', 'right'],\r\n defaultValue: 'left',\r\n },\r\n ],\r\n}\r\n\r\nconst RichText: Block = {\r\n slug: 'richText',\r\n fields: [{ name: 'content', type: 'richText' }],\r\n}\r\n\r\nconst ImageBlock: Block = {\r\n slug: 'image',\r\n fields: [\r\n { name: 'image', type: 'upload', relationTo: 'media', required: true },\r\n { name: 'caption', type: 'text' },\r\n ],\r\n}\r\n\r\n// Container-style blocks (have nested blocks fields)\r\nconst FullWidth: Block = {\r\n slug: 'fullWidth',\r\n fields: [\r\n {\r\n name: 'content',\r\n type: 'blocks',\r\n blocks: [Heading, RichText, ImageBlock],\r\n },\r\n ],\r\n}\r\n\r\nconst HeadingOnly: Block = {\r\n slug: 'headingOnly',\r\n fields: [\r\n {\r\n name: 'content',\r\n type: 'blocks',\r\n maxRows: 1,\r\n blocks: [Heading],\r\n },\r\n ],\r\n}\r\n\r\nconst CtaBanner: Block = {\r\n slug: 'ctaBanner',\r\n fields: [\r\n { name: 'headline', type: 'text', required: true },\r\n { name: 'buttonLabel', type: 'text' },\r\n { name: 'buttonHref', type: 'text' },\r\n ],\r\n}\r\n\r\n// Deeply-nestable container — exercises the recursive path\r\nconst Accordion: Block = {\r\n slug: 'accordion',\r\n fields: [\r\n {\r\n name: 'panels',\r\n type: 'array',\r\n fields: [\r\n { name: 'title', type: 'text' },\r\n {\r\n name: 'body',\r\n type: 'blocks',\r\n blocks: [Heading, RichText, FullWidth],\r\n },\r\n ],\r\n },\r\n ],\r\n}\r\n\r\nconst allBlocks: Block[] = [Heading, RichText, ImageBlock, FullWidth, HeadingOnly, CtaBanner, Accordion]\r\n\r\nconst Posts: CollectionConfig = {\r\n slug: 'posts',\r\n versions: { drafts: true },\r\n fields: [\r\n { name: 'title', type: 'text', required: true },\r\n { name: 'slug', type: 'text', required: true },\r\n { name: 'featured', type: 'checkbox' },\r\n { name: 'category', type: 'relationship', relationTo: 'categories' },\r\n {\r\n name: 'authors',\r\n type: 'relationship',\r\n relationTo: 'authors',\r\n hasMany: true,\r\n },\r\n { name: 'coverImage', type: 'upload', relationTo: 'media' },\r\n {\r\n name: 'tags',\r\n type: 'array',\r\n fields: [{ name: 'tag', type: 'text' }],\r\n },\r\n ],\r\n}\r\n\r\nconst Pages: CollectionConfig = {\r\n slug: 'pages',\r\n versions: { drafts: true },\r\n fields: [\r\n {\r\n type: 'tabs',\r\n tabs: [\r\n {\r\n name: 'hero',\r\n label: 'Hero',\r\n fields: [\r\n { name: 'heroTitle', type: 'text' },\r\n {\r\n name: 'heroSize',\r\n type: 'select',\r\n options: ['small', 'medium', 'large'],\r\n defaultValue: 'medium',\r\n },\r\n ],\r\n },\r\n {\r\n label: 'Content',\r\n fields: [\r\n { name: 'slug', type: 'text', required: true },\r\n { name: 'layout', type: 'blocks', blocks: [FullWidth, HeadingOnly, CtaBanner, Accordion] },\r\n ],\r\n },\r\n ],\r\n },\r\n ],\r\n}\r\n\r\n// ─── introspectCollection ──────────────────────────────────────────\r\n\r\ndescribe('introspectCollection', () => {\r\n it('extracts Posts collection fields, relationships, and draft status', () => {\r\n const schema = introspectCollection(Posts)\r\n\r\n expect(schema.slug).toBe('posts')\r\n expect(schema.hasDrafts).toBe(true)\r\n\r\n const fieldNames = schema.fields.map((f) => f.name)\r\n expect(fieldNames).toContain('title')\r\n expect(fieldNames).toContain('slug')\r\n expect(fieldNames).toContain('featured')\r\n expect(fieldNames).toContain('tags')\r\n\r\n const relFieldNames = schema.relationships.map((r) => r.fieldName)\r\n expect(relFieldNames).toContain('category')\r\n expect(relFieldNames).toContain('authors')\r\n\r\n const cover = schema.relationships.find((r) => r.fieldName === 'coverImage')\r\n expect(cover).toBeDefined()\r\n expect(cover!.relationTo).toBe('media')\r\n\r\n expect(schema.searchableFields).toContain('title')\r\n expect(schema.searchableFields).toContain('slug')\r\n })\r\n\r\n it('extracts Pages collection with tab-nested fields', () => {\r\n const schema = introspectCollection(Pages)\r\n\r\n expect(schema.slug).toBe('pages')\r\n expect(schema.hasDrafts).toBe(true)\r\n\r\n const fieldNames = schema.fields.map((f) => f.name)\r\n expect(fieldNames).toContain('heroTitle')\r\n expect(fieldNames).toContain('slug')\r\n expect(fieldNames).toContain('layout')\r\n })\r\n\r\n it('detects collections without draft support', () => {\r\n const schema = introspectCollection(Categories)\r\n expect(schema.hasDrafts).toBe(false)\r\n })\r\n\r\n it('extracts select field options from Pages heroSize', () => {\r\n const schema = introspectCollection(Pages)\r\n const heroSize = schema.fields.find((f) => f.name === 'heroSize')\r\n expect(heroSize).toBeDefined()\r\n expect(heroSize!.type).toBe('select')\r\n expect(heroSize!.options).toBeDefined()\r\n expect(heroSize!.options!.length).toBe(3)\r\n })\r\n})\r\n\r\n// ─── introspectBlocks (flat catalog) ───────────────────────────────\r\n\r\ndescribe('introspectBlocks', () => {\r\n it('returns a flat catalog of every block with no section/leaf split', () => {\r\n const catalog = introspectBlocks(allBlocks)\r\n const slugs = catalog.blocks.map((b) => b.slug)\r\n expect(slugs).toEqual([\r\n 'heading',\r\n 'richText',\r\n 'image',\r\n 'fullWidth',\r\n 'headingOnly',\r\n 'ctaBanner',\r\n 'accordion',\r\n ])\r\n })\r\n\r\n it('extracts each block\\'s fields including select options', () => {\r\n const catalog = introspectBlocks(allBlocks)\r\n const heading = catalog.blocks.find((b) => b.slug === 'heading')\r\n expect(heading).toBeDefined()\r\n const headingFieldNames = heading!.fields.map((f) => f.name)\r\n expect(headingFieldNames).toEqual(['text', 'level', 'align'])\r\n const level = heading!.fields.find((f) => f.name === 'level')\r\n expect(level!.options).toBeDefined()\r\n })\r\n})\r\n\r\n// ─── buildBlockNestingMap ──────────────────────────────────────────\r\n\r\ndescribe('buildBlockNestingMap', () => {\r\n it('records the layout field on Pages with the slugs it accepts', () => {\r\n const map = buildBlockNestingMap([Pages, Posts], allBlocks)\r\n const pageLayout = map.find(\r\n (e) => e.ownerType === 'collection' && e.owner === 'pages' && e.fieldPath === 'layout',\r\n )\r\n expect(pageLayout).toBeDefined()\r\n expect(pageLayout!.acceptedBlockSlugs).toEqual(['fullWidth', 'headingOnly', 'ctaBanner', 'accordion'])\r\n })\r\n\r\n it('records nested blocks fields inside container blocks', () => {\r\n const map = buildBlockNestingMap([Pages], allBlocks)\r\n\r\n const fullWidthContent = map.find(\r\n (e) => e.ownerType === 'block' && e.owner === 'fullWidth' && e.fieldPath === 'content',\r\n )\r\n expect(fullWidthContent).toBeDefined()\r\n expect(fullWidthContent!.acceptedBlockSlugs).toEqual(['heading', 'richText', 'image'])\r\n\r\n const headingOnly = map.find(\r\n (e) => e.ownerType === 'block' && e.owner === 'headingOnly' && e.fieldPath === 'content',\r\n )\r\n expect(headingOnly!.acceptedBlockSlugs).toEqual(['heading'])\r\n expect(headingOnly!.maxRows).toBe(1)\r\n })\r\n\r\n it('handles arbitrarily-deep nesting via array fields inside blocks', () => {\r\n const map = buildBlockNestingMap([Pages], allBlocks)\r\n\r\n const accordionPanelBody = map.find(\r\n (e) =>\r\n e.ownerType === 'block' && e.owner === 'accordion' && e.fieldPath === 'panels[].body',\r\n )\r\n expect(accordionPanelBody).toBeDefined()\r\n expect(accordionPanelBody!.acceptedBlockSlugs).toEqual(['heading', 'richText', 'fullWidth'])\r\n })\r\n\r\n it('omits unknown slugs not present in the block list', () => {\r\n const Stray: CollectionConfig = {\r\n slug: 'stray',\r\n fields: [\r\n {\r\n name: 'layout',\r\n type: 'blocks',\r\n blocks: [Heading, { slug: 'mystery', fields: [] } as Block],\r\n },\r\n ],\r\n }\r\n const map = buildBlockNestingMap([Stray], [Heading]) // mystery not in catalog\r\n const stray = map.find((e) => e.owner === 'stray' && e.fieldPath === 'layout')\r\n expect(stray!.acceptedBlockSlugs).toEqual(['heading'])\r\n })\r\n\r\n it('omits fixed blocks (no nested blocks fields) from the map', () => {\r\n const map = buildBlockNestingMap([Pages], allBlocks)\r\n const ctaEntries = map.filter((e) => e.owner === 'ctaBanner')\r\n expect(ctaEntries).toHaveLength(0)\r\n })\r\n})\r\n\r\n// ─── buildRelationshipGraph ────────────────────────────────────────\r\n\r\ndescribe('buildRelationshipGraph', () => {\r\n it('builds correct graph from sample collections', () => {\r\n const schemas = introspectCollections([Posts, Pages, Categories, Authors, Media])\r\n const edges = buildRelationshipGraph(schemas)\r\n\r\n const postEdges = edges.filter((e) => e.fromCollection === 'posts')\r\n const postTargets = postEdges.map((e) => e.toCollection)\r\n expect(postTargets).toContain('categories')\r\n expect(postTargets).toContain('authors')\r\n expect(postTargets).toContain('media')\r\n\r\n const authorEdges = edges.filter((e) => e.fromCollection === 'authors')\r\n expect(authorEdges.map((e) => e.toCollection)).toContain('media')\r\n })\r\n})\r\n"],"names":["describe","it","expect","introspectCollection","introspectCollections","introspectBlocks","buildBlockNestingMap","buildRelationshipGraph","Media","slug","upload","fields","name","type","required","Categories","Authors","relationTo","Heading","options","defaultValue","RichText","ImageBlock","FullWidth","blocks","HeadingOnly","maxRows","CtaBanner","Accordion","allBlocks","Posts","versions","drafts","hasMany","Pages","tabs","label","schema","toBe","hasDrafts","fieldNames","map","f","toContain","relFieldNames","relationships","r","fieldName","cover","find","toBeDefined","searchableFields","heroSize","length","catalog","slugs","b","toEqual","heading","headingFieldNames","level","pageLayout","e","ownerType","owner","fieldPath","acceptedBlockSlugs","fullWidthContent","headingOnly","accordionPanelBody","Stray","stray","ctaEntries","filter","toHaveLength","schemas","edges","postEdges","fromCollection","postTargets","toCollection","authorEdges"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/introspection.test.ts"],"sourcesContent":["import { describe, it, expect } from 'vitest'\nimport type { Block, CollectionConfig } from 'payload'\nimport {\n introspectCollection,\n introspectCollections,\n introspectBlocks,\n buildBlockNestingMap,\n buildRelationshipGraph,\n} from '../introspection'\n\n// ─── Sample schema (kept inline so the test is self-contained) ─────\n\nconst Media: CollectionConfig = {\n slug: 'media',\n upload: true,\n fields: [{ name: 'alt', type: 'text', required: true }],\n}\n\nconst Categories: CollectionConfig = {\n slug: 'categories',\n fields: [\n { name: 'name', type: 'text', required: true },\n { name: 'slug', type: 'text', required: true },\n ],\n}\n\nconst Authors: CollectionConfig = {\n slug: 'authors',\n fields: [\n { name: 'name', type: 'text', required: true },\n { name: 'slug', type: 'text', required: true },\n { name: 'avatar', type: 'upload', relationTo: 'media' },\n ],\n}\n\n// Leaf-style blocks\nconst Heading: Block = {\n slug: 'heading',\n fields: [\n { name: 'text', type: 'text', required: true },\n {\n name: 'level',\n type: 'select',\n options: ['h1', 'h2', 'h3'],\n defaultValue: 'h2',\n },\n {\n name: 'align',\n type: 'select',\n options: ['left', 'center', 'right'],\n defaultValue: 'left',\n },\n ],\n}\n\nconst RichText: Block = {\n slug: 'richText',\n fields: [{ name: 'content', type: 'richText' }],\n}\n\nconst ImageBlock: Block = {\n slug: 'image',\n fields: [\n { name: 'image', type: 'upload', relationTo: 'media', required: true },\n { name: 'caption', type: 'text' },\n ],\n}\n\n// Container-style blocks (have nested blocks fields)\nconst FullWidth: Block = {\n slug: 'fullWidth',\n fields: [\n {\n name: 'content',\n type: 'blocks',\n blocks: [Heading, RichText, ImageBlock],\n },\n ],\n}\n\nconst HeadingOnly: Block = {\n slug: 'headingOnly',\n fields: [\n {\n name: 'content',\n type: 'blocks',\n maxRows: 1,\n blocks: [Heading],\n },\n ],\n}\n\nconst CtaBanner: Block = {\n slug: 'ctaBanner',\n fields: [\n { name: 'headline', type: 'text', required: true },\n { name: 'buttonLabel', type: 'text' },\n { name: 'buttonHref', type: 'text' },\n ],\n}\n\n// Deeply-nestable container — exercises the recursive path\nconst Accordion: Block = {\n slug: 'accordion',\n fields: [\n {\n name: 'panels',\n type: 'array',\n fields: [\n { name: 'title', type: 'text' },\n {\n name: 'body',\n type: 'blocks',\n blocks: [Heading, RichText, FullWidth],\n },\n ],\n },\n ],\n}\n\nconst allBlocks: Block[] = [Heading, RichText, ImageBlock, FullWidth, HeadingOnly, CtaBanner, Accordion]\n\nconst Posts: CollectionConfig = {\n slug: 'posts',\n versions: { drafts: true },\n fields: [\n { name: 'title', type: 'text', required: true },\n { name: 'slug', type: 'text', required: true },\n { name: 'featured', type: 'checkbox' },\n { name: 'category', type: 'relationship', relationTo: 'categories' },\n {\n name: 'authors',\n type: 'relationship',\n relationTo: 'authors',\n hasMany: true,\n },\n { name: 'coverImage', type: 'upload', relationTo: 'media' },\n {\n name: 'tags',\n type: 'array',\n fields: [{ name: 'tag', type: 'text' }],\n },\n ],\n}\n\nconst Pages: CollectionConfig = {\n slug: 'pages',\n versions: { drafts: true },\n fields: [\n {\n type: 'tabs',\n tabs: [\n {\n name: 'hero',\n label: 'Hero',\n fields: [\n { name: 'heroTitle', type: 'text' },\n {\n name: 'heroSize',\n type: 'select',\n options: ['small', 'medium', 'large'],\n defaultValue: 'medium',\n },\n ],\n },\n {\n label: 'Content',\n fields: [\n { name: 'slug', type: 'text', required: true },\n { name: 'layout', type: 'blocks', blocks: [FullWidth, HeadingOnly, CtaBanner, Accordion] },\n ],\n },\n ],\n },\n ],\n}\n\n// ─── introspectCollection ──────────────────────────────────────────\n\ndescribe('introspectCollection', () => {\n it('extracts Posts collection fields, relationships, and draft status', () => {\n const schema = introspectCollection(Posts)\n\n expect(schema.slug).toBe('posts')\n expect(schema.hasDrafts).toBe(true)\n\n const fieldNames = schema.fields.map((f) => f.name)\n expect(fieldNames).toContain('title')\n expect(fieldNames).toContain('slug')\n expect(fieldNames).toContain('featured')\n expect(fieldNames).toContain('tags')\n\n const relFieldNames = schema.relationships.map((r) => r.fieldName)\n expect(relFieldNames).toContain('category')\n expect(relFieldNames).toContain('authors')\n\n const cover = schema.relationships.find((r) => r.fieldName === 'coverImage')\n expect(cover).toBeDefined()\n expect(cover!.relationTo).toBe('media')\n\n expect(schema.searchableFields).toContain('title')\n expect(schema.searchableFields).toContain('slug')\n })\n\n it('extracts Pages collection with tab-nested fields', () => {\n const schema = introspectCollection(Pages)\n\n expect(schema.slug).toBe('pages')\n expect(schema.hasDrafts).toBe(true)\n\n const fieldNames = schema.fields.map((f) => f.name)\n expect(fieldNames).toContain('heroTitle')\n expect(fieldNames).toContain('slug')\n expect(fieldNames).toContain('layout')\n })\n\n it('detects collections without draft support', () => {\n const schema = introspectCollection(Categories)\n expect(schema.hasDrafts).toBe(false)\n })\n\n it('extracts select field options from Pages heroSize', () => {\n const schema = introspectCollection(Pages)\n const heroSize = schema.fields.find((f) => f.name === 'heroSize')\n expect(heroSize).toBeDefined()\n expect(heroSize!.type).toBe('select')\n expect(heroSize!.options).toBeDefined()\n expect(heroSize!.options!.length).toBe(3)\n })\n})\n\n// ─── introspectBlocks (flat catalog) ───────────────────────────────\n\ndescribe('introspectBlocks', () => {\n it('returns a flat catalog of every block with no section/leaf split', () => {\n const catalog = introspectBlocks(allBlocks)\n const slugs = catalog.blocks.map((b) => b.slug)\n expect(slugs).toEqual([\n 'heading',\n 'richText',\n 'image',\n 'fullWidth',\n 'headingOnly',\n 'ctaBanner',\n 'accordion',\n ])\n })\n\n it('extracts each block\\'s fields including select options', () => {\n const catalog = introspectBlocks(allBlocks)\n const heading = catalog.blocks.find((b) => b.slug === 'heading')\n expect(heading).toBeDefined()\n const headingFieldNames = heading!.fields.map((f) => f.name)\n expect(headingFieldNames).toEqual(['text', 'level', 'align'])\n const level = heading!.fields.find((f) => f.name === 'level')\n expect(level!.options).toBeDefined()\n })\n})\n\n// ─── buildBlockNestingMap ──────────────────────────────────────────\n\ndescribe('buildBlockNestingMap', () => {\n it('records the layout field on Pages with the slugs it accepts', () => {\n const map = buildBlockNestingMap([Pages, Posts], allBlocks)\n const pageLayout = map.find(\n (e) => e.ownerType === 'collection' && e.owner === 'pages' && e.fieldPath === 'layout',\n )\n expect(pageLayout).toBeDefined()\n expect(pageLayout!.acceptedBlockSlugs).toEqual(['fullWidth', 'headingOnly', 'ctaBanner', 'accordion'])\n })\n\n it('records nested blocks fields inside container blocks', () => {\n const map = buildBlockNestingMap([Pages], allBlocks)\n\n const fullWidthContent = map.find(\n (e) => e.ownerType === 'block' && e.owner === 'fullWidth' && e.fieldPath === 'content',\n )\n expect(fullWidthContent).toBeDefined()\n expect(fullWidthContent!.acceptedBlockSlugs).toEqual(['heading', 'richText', 'image'])\n\n const headingOnly = map.find(\n (e) => e.ownerType === 'block' && e.owner === 'headingOnly' && e.fieldPath === 'content',\n )\n expect(headingOnly!.acceptedBlockSlugs).toEqual(['heading'])\n expect(headingOnly!.maxRows).toBe(1)\n })\n\n it('handles arbitrarily-deep nesting via array fields inside blocks', () => {\n const map = buildBlockNestingMap([Pages], allBlocks)\n\n const accordionPanelBody = map.find(\n (e) =>\n e.ownerType === 'block' && e.owner === 'accordion' && e.fieldPath === 'panels[].body',\n )\n expect(accordionPanelBody).toBeDefined()\n expect(accordionPanelBody!.acceptedBlockSlugs).toEqual(['heading', 'richText', 'fullWidth'])\n })\n\n it('omits unknown slugs not present in the block list', () => {\n const Stray: CollectionConfig = {\n slug: 'stray',\n fields: [\n {\n name: 'layout',\n type: 'blocks',\n blocks: [Heading, { slug: 'mystery', fields: [] } as Block],\n },\n ],\n }\n const map = buildBlockNestingMap([Stray], [Heading]) // mystery not in catalog\n const stray = map.find((e) => e.owner === 'stray' && e.fieldPath === 'layout')\n expect(stray!.acceptedBlockSlugs).toEqual(['heading'])\n })\n\n it('omits fixed blocks (no nested blocks fields) from the map', () => {\n const map = buildBlockNestingMap([Pages], allBlocks)\n const ctaEntries = map.filter((e) => e.owner === 'ctaBanner')\n expect(ctaEntries).toHaveLength(0)\n })\n})\n\n// ─── buildRelationshipGraph ────────────────────────────────────────\n\ndescribe('buildRelationshipGraph', () => {\n it('builds correct graph from sample collections', () => {\n const schemas = introspectCollections([Posts, Pages, Categories, Authors, Media])\n const edges = buildRelationshipGraph(schemas)\n\n const postEdges = edges.filter((e) => e.fromCollection === 'posts')\n const postTargets = postEdges.map((e) => e.toCollection)\n expect(postTargets).toContain('categories')\n expect(postTargets).toContain('authors')\n expect(postTargets).toContain('media')\n\n const authorEdges = edges.filter((e) => e.fromCollection === 'authors')\n expect(authorEdges.map((e) => e.toCollection)).toContain('media')\n })\n})\n"],"names":["describe","it","expect","introspectCollection","introspectCollections","introspectBlocks","buildBlockNestingMap","buildRelationshipGraph","Media","slug","upload","fields","name","type","required","Categories","Authors","relationTo","Heading","options","defaultValue","RichText","ImageBlock","FullWidth","blocks","HeadingOnly","maxRows","CtaBanner","Accordion","allBlocks","Posts","versions","drafts","hasMany","Pages","tabs","label","schema","toBe","hasDrafts","fieldNames","map","f","toContain","relFieldNames","relationships","r","fieldName","cover","find","toBeDefined","searchableFields","heroSize","length","catalog","slugs","b","toEqual","heading","headingFieldNames","level","pageLayout","e","ownerType","owner","fieldPath","acceptedBlockSlugs","fullWidthContent","headingOnly","accordionPanelBody","Stray","stray","ctaEntries","filter","toHaveLength","schemas","edges","postEdges","fromCollection","postTargets","toCollection","authorEdges"],"mappings":"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"}
|
package/dist/draft-workflow.d.ts
CHANGED
|
@@ -47,10 +47,11 @@ export declare function getDraftBehavior(collection: CollectionConfig, options?:
|
|
|
47
47
|
* Generates the mcpCollections config object for the official mcpPlugin.
|
|
48
48
|
*
|
|
49
49
|
* For each non-excluded collection:
|
|
50
|
-
* -
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
50
|
+
* - Enables `find` / `create` / `delete`; disables the official plugin's
|
|
51
|
+
* raw `update<Resource>` tool universally. The toolkit's `updateDocument`
|
|
52
|
+
* and `patchLayout` cover updates via the local API (and survive the
|
|
53
|
+
* upload-field / schema-conversion bugs in the official plugin's update
|
|
54
|
+
* path). Draft semantics are preserved by `publishDraft`.
|
|
54
55
|
* - For draft collections: attaches an `overrideResponse` that appends a
|
|
55
56
|
* preview URL — sourced from the collection's own livePreview/preview
|
|
56
57
|
* function — to draft documents. Falls back to a generic admin-panel
|
package/dist/draft-workflow.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { hasCollectionDrafts } from './introspection';
|
|
2
|
-
/**
|
|
3
|
-
* Determines the draft behavior for a collection.
|
|
4
|
-
*
|
|
5
|
-
* - No drafts configured → 'publish' (raw update allowed; no draft concept)
|
|
6
|
-
* - Drafts configured + override given → use override
|
|
7
|
-
* - Drafts configured + no override → 'always-draft' (raw update locked)
|
|
2
|
+
/**
|
|
3
|
+
* Determines the draft behavior for a collection.
|
|
4
|
+
*
|
|
5
|
+
* - No drafts configured → 'publish' (raw update allowed; no draft concept)
|
|
6
|
+
* - Drafts configured + override given → use override
|
|
7
|
+
* - Drafts configured + no override → 'always-draft' (raw update locked)
|
|
8
8
|
*/ export function getDraftBehavior(collection, options) {
|
|
9
9
|
if (!hasCollectionDrafts(collection)) return 'publish';
|
|
10
10
|
const override = options?.draftBehavior?.[collection.slug];
|
|
11
11
|
if (override) return override;
|
|
12
12
|
return 'always-draft';
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Build a preview URL for a draft document by delegating to the collection's
|
|
16
|
-
* own configured preview URL function. Tries `admin.livePreview.url` first
|
|
17
|
-
* (the modern API), then `admin.preview` (the older `GeneratePreviewURL`).
|
|
18
|
-
*
|
|
19
|
-
* If neither is configured, or the function returns null/undefined/empty,
|
|
20
|
-
* returns null and the override response will skip preview injection.
|
|
14
|
+
/**
|
|
15
|
+
* Build a preview URL for a draft document by delegating to the collection's
|
|
16
|
+
* own configured preview URL function. Tries `admin.livePreview.url` first
|
|
17
|
+
* (the modern API), then `admin.preview` (the older `GeneratePreviewURL`).
|
|
18
|
+
*
|
|
19
|
+
* If neither is configured, or the function returns null/undefined/empty,
|
|
20
|
+
* returns null and the override response will skip preview injection.
|
|
21
21
|
*/ async function resolvePreviewUrl(collection, doc, req, siteUrl) {
|
|
22
22
|
const admin = collection.admin ?? {};
|
|
23
23
|
const locale = req.locale ?? 'en';
|
|
@@ -87,20 +87,21 @@ function createOverrideResponse(collection, siteUrl) {
|
|
|
87
87
|
};
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
|
-
/**
|
|
91
|
-
* Generates the mcpCollections config object for the official mcpPlugin.
|
|
92
|
-
*
|
|
93
|
-
* For each non-excluded collection:
|
|
94
|
-
* -
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
90
|
+
/**
|
|
91
|
+
* Generates the mcpCollections config object for the official mcpPlugin.
|
|
92
|
+
*
|
|
93
|
+
* For each non-excluded collection:
|
|
94
|
+
* - Enables `find` / `create` / `delete`; disables the official plugin's
|
|
95
|
+
* raw `update<Resource>` tool universally. The toolkit's `updateDocument`
|
|
96
|
+
* and `patchLayout` cover updates via the local API (and survive the
|
|
97
|
+
* upload-field / schema-conversion bugs in the official plugin's update
|
|
98
|
+
* path). Draft semantics are preserved by `publishDraft`.
|
|
99
|
+
* - For draft collections: attaches an `overrideResponse` that appends a
|
|
100
|
+
* preview URL — sourced from the collection's own livePreview/preview
|
|
101
|
+
* function — to draft documents. Falls back to a generic admin-panel
|
|
102
|
+
* message when no preview function is configured.
|
|
103
|
+
*
|
|
104
|
+
* @returns Map of slug → MCP collection config, plus the set of draft slugs
|
|
104
105
|
*/ export function generateMcpCollectionConfigs(collections, options) {
|
|
105
106
|
const mcpCollections = {};
|
|
106
107
|
const draftCollections = new Set();
|
|
@@ -116,10 +117,17 @@ function createOverrideResponse(collection, siteUrl) {
|
|
|
116
117
|
if (behavior !== 'publish') {
|
|
117
118
|
draftCollections.add(collection.slug);
|
|
118
119
|
}
|
|
120
|
+
// Always disable the official plugin's per-collection `update<Resource>` tool.
|
|
121
|
+
// It calls `convertCollectionSchemaToZod` and crashes on any collection
|
|
122
|
+
// whose JSON schema can't be losslessly converted (richText, upload,
|
|
123
|
+
// blocks fields → fallback returns `z.record()`, then `.partial()` throws).
|
|
124
|
+
// The toolkit's `updateDocument` and `patchLayout` cover updates via the
|
|
125
|
+
// local API and survive the upload-field / schema-conversion bugs, so the
|
|
126
|
+
// raw update tool is redundant. See README "What it adds → updateDocument".
|
|
119
127
|
const enabled = {
|
|
120
128
|
find: true,
|
|
121
129
|
create: true,
|
|
122
|
-
update:
|
|
130
|
+
update: false,
|
|
123
131
|
delete: true
|
|
124
132
|
};
|
|
125
133
|
const config = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/draft-workflow.ts"],"sourcesContent":["import type { CollectionConfig, PayloadRequest } from 'payload'\r\nimport { hasCollectionDrafts } from './introspection'\r\n\r\n/** MCP response shape used by overrideResponse */\r\ninterface McpResponse {\r\n content: Array<{ text: string; type: string }>\r\n}\r\n\r\n/** Per-collection MCP config with enabled operations and optional overrideResponse */\r\ninterface McpCollectionConfig {\r\n description: string\r\n enabled: {\r\n create: boolean\r\n delete: boolean\r\n find: boolean\r\n update: boolean\r\n }\r\n overrideResponse?: (\r\n response: McpResponse,\r\n doc: Record<string, unknown>,\r\n req: PayloadRequest,\r\n ) => McpResponse | Promise<McpResponse>\r\n}\r\n\r\ninterface GenerateOptions {\r\n /**\r\n * Optional absolute base URL prepended to relative preview paths returned\r\n * by the collection's own preview URL function. Resolved upstream from\r\n * (in order): `options.preview.siteUrl`, `incomingConfig.serverURL`,\r\n * `process.env.NEXT_PUBLIC_SERVER_URL`, `process.env.SITE_URL`. May be\r\n * undefined — relative-path returns will then be skipped.\r\n */\r\n siteUrl?: string\r\n /** Per-collection draft behavior overrides */\r\n draftBehavior?: Record<string, 'always-draft' | 'always-publish'>\r\n /** Collection slugs to exclude from MCP */\r\n excludeCollections?: string[]\r\n /** Disable preview URL injection entirely */\r\n previewDisabled?: boolean\r\n}\r\n\r\n/**\r\n * Determines the draft behavior for a collection.\r\n *\r\n * - No drafts configured → 'publish' (raw update allowed; no draft concept)\r\n * - Drafts configured + override given → use override\r\n * - Drafts configured + no override → 'always-draft' (raw update locked)\r\n */\r\nexport function getDraftBehavior(\r\n collection: CollectionConfig,\r\n options?: { draftBehavior?: Record<string, 'always-draft' | 'always-publish'> },\r\n): 'always-draft' | 'always-publish' | 'publish' {\r\n if (!hasCollectionDrafts(collection)) return 'publish'\r\n\r\n const override = options?.draftBehavior?.[collection.slug]\r\n if (override) return override\r\n\r\n return 'always-draft'\r\n}\r\n\r\n/**\r\n * Build a preview URL for a draft document by delegating to the collection's\r\n * own configured preview URL function. Tries `admin.livePreview.url` first\r\n * (the modern API), then `admin.preview` (the older `GeneratePreviewURL`).\r\n *\r\n * If neither is configured, or the function returns null/undefined/empty,\r\n * returns null and the override response will skip preview injection.\r\n */\r\nasync function resolvePreviewUrl(\r\n collection: CollectionConfig,\r\n doc: Record<string, unknown>,\r\n req: PayloadRequest,\r\n siteUrl: string | undefined,\r\n): Promise<string | null> {\r\n const admin = (collection.admin ?? {}) as Record<string, any>\r\n const locale = (req as any).locale ?? 'en'\r\n\r\n let raw: string | null | undefined\r\n\r\n const livePreviewUrl = admin.livePreview?.url\r\n if (typeof livePreviewUrl === 'function') {\r\n try {\r\n raw = await livePreviewUrl({\r\n data: doc,\r\n locale: { code: locale, label: locale },\r\n req,\r\n payload: req.payload,\r\n collectionConfig: collection as any,\r\n })\r\n } catch {\r\n raw = null\r\n }\r\n } else if (typeof livePreviewUrl === 'string') {\r\n raw = livePreviewUrl\r\n }\r\n\r\n if (!raw && typeof admin.preview === 'function') {\r\n try {\r\n raw = await admin.preview(doc, {\r\n locale,\r\n req,\r\n token: null,\r\n })\r\n } catch {\r\n raw = null\r\n }\r\n }\r\n\r\n if (!raw || typeof raw !== 'string') return null\r\n\r\n if (raw.startsWith('http://') || raw.startsWith('https://')) {\r\n return raw\r\n }\r\n\r\n if (!siteUrl) return null\r\n\r\n const base = siteUrl.endsWith('/') ? siteUrl.slice(0, -1) : siteUrl\r\n const path = raw.startsWith('/') ? raw : `/${raw}`\r\n return `${base}${path}`\r\n}\r\n\r\nfunction createOverrideResponse(\r\n collection: CollectionConfig,\r\n siteUrl: string | undefined,\r\n): McpCollectionConfig['overrideResponse'] {\r\n return async (response, doc, req): Promise<McpResponse> => {\r\n if (doc._status !== 'draft') return response\r\n\r\n const previewUrl = await resolvePreviewUrl(collection, doc, req, siteUrl)\r\n if (!previewUrl) {\r\n return {\r\n content: [\r\n ...response.content,\r\n {\r\n type: 'text',\r\n text: '\\n📋 This document is a draft. Use the admin panel to preview it.',\r\n },\r\n ],\r\n }\r\n }\r\n\r\n return {\r\n content: [\r\n ...response.content,\r\n {\r\n type: 'text',\r\n text: `\\n📋 This document is a draft. Preview it here: ${previewUrl}`,\r\n },\r\n ],\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Generates the mcpCollections config object for the official mcpPlugin.\r\n *\r\n * For each non-excluded collection:\r\n * - Determines enabled CRUD operations based on draft behavior\r\n * - For 'always-draft' collections: disables raw `update` to force clients\r\n * through publishDraft / patchLayout / updateDocument (which preserve\r\n * draft semantics)\r\n * - For draft collections: attaches an `overrideResponse` that appends a\r\n * preview URL — sourced from the collection's own livePreview/preview\r\n * function — to draft documents. Falls back to a generic admin-panel\r\n * message when no preview function is configured.\r\n *\r\n * @returns Map of slug → MCP collection config, plus the set of draft slugs\r\n */\r\nexport function generateMcpCollectionConfigs(\r\n collections: CollectionConfig[],\r\n options: GenerateOptions,\r\n): {\r\n mcpCollections: Record<string, McpCollectionConfig>\r\n draftCollections: Set<string>\r\n} {\r\n const mcpCollections: Record<string, McpCollectionConfig> = {}\r\n const draftCollections = new Set<string>()\r\n\r\n const excludeSlugs = new Set([\r\n 'payload-mcp-api-keys',\r\n ...(options.excludeCollections ?? []),\r\n ])\r\n\r\n for (const collection of collections) {\r\n if (excludeSlugs.has(collection.slug)) continue\r\n\r\n // Auth-enabled collections are users — never expose them via MCP.\r\n if ((collection as any).auth) continue\r\n\r\n const behavior = getDraftBehavior(collection, options)\r\n\r\n if (behavior !== 'publish') {\r\n draftCollections.add(collection.slug)\r\n }\r\n\r\n const enabled = {\r\n find: true,\r\n create: true,\r\n update: behavior !== 'always-draft',\r\n delete: true,\r\n }\r\n\r\n const config: McpCollectionConfig = {\r\n description: `Manage ${collection.slug} content`,\r\n enabled,\r\n }\r\n\r\n if (draftCollections.has(collection.slug) && !options.previewDisabled) {\r\n config.overrideResponse = createOverrideResponse(collection, options.siteUrl)\r\n }\r\n\r\n mcpCollections[collection.slug] = config\r\n }\r\n\r\n return { mcpCollections, draftCollections }\r\n}\r\n"],"names":["hasCollectionDrafts","getDraftBehavior","collection","options","override","draftBehavior","slug","resolvePreviewUrl","doc","req","siteUrl","admin","locale","raw","livePreviewUrl","livePreview","url","data","code","label","payload","collectionConfig","preview","token","startsWith","base","endsWith","slice","path","createOverrideResponse","response","_status","previewUrl","content","type","text","generateMcpCollectionConfigs","collections","mcpCollections","draftCollections","Set","excludeSlugs","excludeCollections","has","auth","behavior","add","enabled","find","create","update","delete","config","description","previewDisabled","overrideResponse"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/draft-workflow.ts"],"sourcesContent":["import type { CollectionConfig, PayloadRequest } from 'payload'\nimport { hasCollectionDrafts } from './introspection'\n\n/** MCP response shape used by overrideResponse */\ninterface McpResponse {\n content: Array<{ text: string; type: string }>\n}\n\n/** Per-collection MCP config with enabled operations and optional overrideResponse */\ninterface McpCollectionConfig {\n description: string\n enabled: {\n create: boolean\n delete: boolean\n find: boolean\n update: boolean\n }\n overrideResponse?: (\n response: McpResponse,\n doc: Record<string, unknown>,\n req: PayloadRequest,\n ) => McpResponse | Promise<McpResponse>\n}\n\ninterface GenerateOptions {\n /**\n * Optional absolute base URL prepended to relative preview paths returned\n * by the collection's own preview URL function. Resolved upstream from\n * (in order): `options.preview.siteUrl`, `incomingConfig.serverURL`,\n * `process.env.NEXT_PUBLIC_SERVER_URL`, `process.env.SITE_URL`. May be\n * undefined — relative-path returns will then be skipped.\n */\n siteUrl?: string\n /** Per-collection draft behavior overrides */\n draftBehavior?: Record<string, 'always-draft' | 'always-publish'>\n /** Collection slugs to exclude from MCP */\n excludeCollections?: string[]\n /** Disable preview URL injection entirely */\n previewDisabled?: boolean\n}\n\n/**\n * Determines the draft behavior for a collection.\n *\n * - No drafts configured → 'publish' (raw update allowed; no draft concept)\n * - Drafts configured + override given → use override\n * - Drafts configured + no override → 'always-draft' (raw update locked)\n */\nexport function getDraftBehavior(\n collection: CollectionConfig,\n options?: { draftBehavior?: Record<string, 'always-draft' | 'always-publish'> },\n): 'always-draft' | 'always-publish' | 'publish' {\n if (!hasCollectionDrafts(collection)) return 'publish'\n\n const override = options?.draftBehavior?.[collection.slug]\n if (override) return override\n\n return 'always-draft'\n}\n\n/**\n * Build a preview URL for a draft document by delegating to the collection's\n * own configured preview URL function. Tries `admin.livePreview.url` first\n * (the modern API), then `admin.preview` (the older `GeneratePreviewURL`).\n *\n * If neither is configured, or the function returns null/undefined/empty,\n * returns null and the override response will skip preview injection.\n */\nasync function resolvePreviewUrl(\n collection: CollectionConfig,\n doc: Record<string, unknown>,\n req: PayloadRequest,\n siteUrl: string | undefined,\n): Promise<string | null> {\n const admin = (collection.admin ?? {}) as Record<string, any>\n const locale = (req as any).locale ?? 'en'\n\n let raw: string | null | undefined\n\n const livePreviewUrl = admin.livePreview?.url\n if (typeof livePreviewUrl === 'function') {\n try {\n raw = await livePreviewUrl({\n data: doc,\n locale: { code: locale, label: locale },\n req,\n payload: req.payload,\n collectionConfig: collection as any,\n })\n } catch {\n raw = null\n }\n } else if (typeof livePreviewUrl === 'string') {\n raw = livePreviewUrl\n }\n\n if (!raw && typeof admin.preview === 'function') {\n try {\n raw = await admin.preview(doc, {\n locale,\n req,\n token: null,\n })\n } catch {\n raw = null\n }\n }\n\n if (!raw || typeof raw !== 'string') return null\n\n if (raw.startsWith('http://') || raw.startsWith('https://')) {\n return raw\n }\n\n if (!siteUrl) return null\n\n const base = siteUrl.endsWith('/') ? siteUrl.slice(0, -1) : siteUrl\n const path = raw.startsWith('/') ? raw : `/${raw}`\n return `${base}${path}`\n}\n\nfunction createOverrideResponse(\n collection: CollectionConfig,\n siteUrl: string | undefined,\n): McpCollectionConfig['overrideResponse'] {\n return async (response, doc, req): Promise<McpResponse> => {\n if (doc._status !== 'draft') return response\n\n const previewUrl = await resolvePreviewUrl(collection, doc, req, siteUrl)\n if (!previewUrl) {\n return {\n content: [\n ...response.content,\n {\n type: 'text',\n text: '\\n📋 This document is a draft. Use the admin panel to preview it.',\n },\n ],\n }\n }\n\n return {\n content: [\n ...response.content,\n {\n type: 'text',\n text: `\\n📋 This document is a draft. Preview it here: ${previewUrl}`,\n },\n ],\n }\n }\n}\n\n/**\n * Generates the mcpCollections config object for the official mcpPlugin.\n *\n * For each non-excluded collection:\n * - Enables `find` / `create` / `delete`; disables the official plugin's\n * raw `update<Resource>` tool universally. The toolkit's `updateDocument`\n * and `patchLayout` cover updates via the local API (and survive the\n * upload-field / schema-conversion bugs in the official plugin's update\n * path). Draft semantics are preserved by `publishDraft`.\n * - For draft collections: attaches an `overrideResponse` that appends a\n * preview URL — sourced from the collection's own livePreview/preview\n * function — to draft documents. Falls back to a generic admin-panel\n * message when no preview function is configured.\n *\n * @returns Map of slug → MCP collection config, plus the set of draft slugs\n */\nexport function generateMcpCollectionConfigs(\n collections: CollectionConfig[],\n options: GenerateOptions,\n): {\n mcpCollections: Record<string, McpCollectionConfig>\n draftCollections: Set<string>\n} {\n const mcpCollections: Record<string, McpCollectionConfig> = {}\n const draftCollections = new Set<string>()\n\n const excludeSlugs = new Set([\n 'payload-mcp-api-keys',\n ...(options.excludeCollections ?? []),\n ])\n\n for (const collection of collections) {\n if (excludeSlugs.has(collection.slug)) continue\n\n // Auth-enabled collections are users — never expose them via MCP.\n if ((collection as any).auth) continue\n\n const behavior = getDraftBehavior(collection, options)\n\n if (behavior !== 'publish') {\n draftCollections.add(collection.slug)\n }\n\n // Always disable the official plugin's per-collection `update<Resource>` tool.\n // It calls `convertCollectionSchemaToZod` and crashes on any collection\n // whose JSON schema can't be losslessly converted (richText, upload,\n // blocks fields → fallback returns `z.record()`, then `.partial()` throws).\n // The toolkit's `updateDocument` and `patchLayout` cover updates via the\n // local API and survive the upload-field / schema-conversion bugs, so the\n // raw update tool is redundant. See README \"What it adds → updateDocument\".\n const enabled = {\n find: true,\n create: true,\n update: false,\n delete: true,\n }\n\n const config: McpCollectionConfig = {\n description: `Manage ${collection.slug} content`,\n enabled,\n }\n\n if (draftCollections.has(collection.slug) && !options.previewDisabled) {\n config.overrideResponse = createOverrideResponse(collection, options.siteUrl)\n }\n\n mcpCollections[collection.slug] = config\n }\n\n return { mcpCollections, draftCollections }\n}\n"],"names":["hasCollectionDrafts","getDraftBehavior","collection","options","override","draftBehavior","slug","resolvePreviewUrl","doc","req","siteUrl","admin","locale","raw","livePreviewUrl","livePreview","url","data","code","label","payload","collectionConfig","preview","token","startsWith","base","endsWith","slice","path","createOverrideResponse","response","_status","previewUrl","content","type","text","generateMcpCollectionConfigs","collections","mcpCollections","draftCollections","Set","excludeSlugs","excludeCollections","has","auth","behavior","add","enabled","find","create","update","delete","config","description","previewDisabled","overrideResponse"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -12,20 +12,20 @@ import { createSearchContentTool } from './tools/search-content';
|
|
|
12
12
|
import { createUpdateDocumentTool } from './tools/update-document';
|
|
13
13
|
import { createUploadMediaTool } from './tools/upload-media';
|
|
14
14
|
import { createListVersionsTool, createRestoreVersionTool } from './tools/versions';
|
|
15
|
-
/**
|
|
16
|
-
* Payload MCP Toolkit
|
|
17
|
-
*
|
|
18
|
-
* Layered on top of the official @payloadcms/plugin-mcp. The toolkit
|
|
19
|
-
* introspects your Payload config and registers schema-aware prompts,
|
|
20
|
-
* resources, and tools so AI clients can drive the CMS without
|
|
21
|
-
* hand-built plumbing.
|
|
22
|
-
*
|
|
23
|
-
* Zero-config usage:
|
|
24
|
-
* ```ts
|
|
25
|
-
* plugins: [contentToolkitPlugin()]
|
|
26
|
-
* ```
|
|
27
|
-
*
|
|
28
|
-
* Every option below is an optional escape hatch — see ContentToolkitOptions.
|
|
15
|
+
/**
|
|
16
|
+
* Payload MCP Toolkit
|
|
17
|
+
*
|
|
18
|
+
* Layered on top of the official @payloadcms/plugin-mcp. The toolkit
|
|
19
|
+
* introspects your Payload config and registers schema-aware prompts,
|
|
20
|
+
* resources, and tools so AI clients can drive the CMS without
|
|
21
|
+
* hand-built plumbing.
|
|
22
|
+
*
|
|
23
|
+
* Zero-config usage:
|
|
24
|
+
* ```ts
|
|
25
|
+
* plugins: [contentToolkitPlugin()]
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Every option below is an optional escape hatch — see ContentToolkitOptions.
|
|
29
29
|
*/ export function contentToolkitPlugin(options = {}) {
|
|
30
30
|
return (incomingConfig)=>{
|
|
31
31
|
const collections = incomingConfig.collections ?? [];
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Block, CollectionConfig, Config, Plugin } from 'payload'\
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Block, CollectionConfig, Config, Plugin } from 'payload'\nimport { mcpPlugin } from '@payloadcms/plugin-mcp'\nimport type { ContentToolkitOptions } from './types'\nimport {\n introspectCollections,\n introspectBlocks,\n buildBlockNestingMap,\n buildRelationshipGraph,\n} from './introspection'\nimport { generatePrompts } from './prompts'\nimport { generateResources } from './resources'\nimport { generateMcpCollectionConfigs } from './draft-workflow'\nimport { createPatchLayoutTool } from './tools/patch-layout'\nimport { createPublishDraftTool } from './tools/publish-draft'\nimport { createResolveReferenceTool } from './tools/resolve-reference'\nimport { createSafeDeleteTool } from './tools/safe-delete'\nimport { createSchedulePublishTool } from './tools/schedule-publish'\nimport { createSearchContentTool } from './tools/search-content'\nimport { createUpdateDocumentTool } from './tools/update-document'\nimport { createUploadMediaTool } from './tools/upload-media'\nimport { createListVersionsTool, createRestoreVersionTool } from './tools/versions'\n\n/**\n * Payload MCP Toolkit\n *\n * Layered on top of the official @payloadcms/plugin-mcp. The toolkit\n * introspects your Payload config and registers schema-aware prompts,\n * resources, and tools so AI clients can drive the CMS without\n * hand-built plumbing.\n *\n * Zero-config usage:\n * ```ts\n * plugins: [contentToolkitPlugin()]\n * ```\n *\n * Every option below is an optional escape hatch — see ContentToolkitOptions.\n */\nexport function contentToolkitPlugin(options: ContentToolkitOptions = {}): Plugin {\n return (incomingConfig: Config): Config => {\n const collections = (incomingConfig.collections ?? []) as CollectionConfig[]\n const allBlocks = (incomingConfig.blocks ?? []) as Block[]\n\n const collectionSchemas = introspectCollections(collections)\n const blockCatalog = introspectBlocks(allBlocks)\n const blockNesting = buildBlockNestingMap(collections, allBlocks)\n const relationships = buildRelationshipGraph(collectionSchemas)\n\n // Preview siteUrl resolves: explicit option → Payload serverURL → env vars.\n // May be undefined; relative-path preview URLs are skipped in that case.\n const previewSiteUrl =\n options.preview?.siteUrl ??\n incomingConfig.serverURL ??\n process.env.NEXT_PUBLIC_SERVER_URL ??\n process.env.SITE_URL\n\n const prompts = generatePrompts(\n collectionSchemas,\n blockCatalog,\n blockNesting,\n relationships,\n options.domainPrompts,\n )\n const resources = generateResources(\n collectionSchemas,\n blockCatalog,\n blockNesting,\n relationships,\n )\n\n const { mcpCollections, draftCollections } = generateMcpCollectionConfigs(collections, {\n siteUrl: previewSiteUrl,\n draftBehavior: options.draftBehavior,\n excludeCollections: options.exclude?.collections,\n previewDisabled: options.preview?.disabled,\n })\n\n const searchableCollections = new Map<string, string[]>()\n for (const [slug, schema] of collectionSchemas) {\n if (schema.searchableFields.length > 0) {\n searchableCollections.set(slug, schema.searchableFields)\n }\n }\n\n const tools: any[] = [\n createPatchLayoutTool(blockCatalog, blockNesting, draftCollections),\n createPublishDraftTool(draftCollections),\n createResolveReferenceTool(searchableCollections),\n createSafeDeleteTool(relationships),\n createSearchContentTool(collectionSchemas),\n createUpdateDocumentTool(collectionSchemas, draftCollections),\n createUploadMediaTool({\n maxFileSize: options.mediaUpload?.maxFileSize,\n collectionSlug: options.mediaUpload?.collectionSlug,\n }),\n createListVersionsTool(draftCollections),\n createRestoreVersionTool(draftCollections),\n ]\n\n const schedulePublish = createSchedulePublishTool(collectionSchemas, draftCollections)\n if (schedulePublish) tools.push(schedulePublish)\n\n const mcpGlobals: Record<string, { enabled: boolean; description?: string }> = {}\n const excludeGlobalSlugs = new Set(options.exclude?.globals ?? [])\n for (const global of (incomingConfig.globals ?? []) as Array<{ slug: string }>) {\n if (excludeGlobalSlugs.has(global.slug)) continue\n mcpGlobals[global.slug] = {\n enabled: true,\n description: `Manage ${global.slug} global settings`,\n }\n }\n\n // overrideAuth rebinds req.user from the API key's linked user so our\n // custom tools' `overrideAccess: false` checks run against the right\n // identity. userCollection passthrough lets the official plugin fall\n // back to `incomingConfig.admin.user`.\n const withMcp = mcpPlugin({\n collections: mcpCollections as any,\n globals: mcpGlobals as any,\n userCollection: options.userCollection as any,\n mcp: {\n tools: tools as any[],\n prompts: prompts as any[],\n resources: resources as any[],\n },\n overrideAuth: async (req, getDefault) => {\n const settings = await getDefault()\n req.user = (settings as any).user\n return settings\n },\n })\n\n return withMcp(incomingConfig)\n }\n}\n\nexport type {\n ContentToolkitOptions,\n DomainPrompt,\n CollectionSchema,\n BlockCatalog,\n BlockSchema,\n BlockNestingMap,\n BlockNestingEdge,\n RelationshipEdge,\n FieldSchema,\n} from './types'\n"],"names":["mcpPlugin","introspectCollections","introspectBlocks","buildBlockNestingMap","buildRelationshipGraph","generatePrompts","generateResources","generateMcpCollectionConfigs","createPatchLayoutTool","createPublishDraftTool","createResolveReferenceTool","createSafeDeleteTool","createSchedulePublishTool","createSearchContentTool","createUpdateDocumentTool","createUploadMediaTool","createListVersionsTool","createRestoreVersionTool","contentToolkitPlugin","options","incomingConfig","collections","allBlocks","blocks","collectionSchemas","blockCatalog","blockNesting","relationships","previewSiteUrl","preview","siteUrl","serverURL","process","env","NEXT_PUBLIC_SERVER_URL","SITE_URL","prompts","domainPrompts","resources","mcpCollections","draftCollections","draftBehavior","excludeCollections","exclude","previewDisabled","disabled","searchableCollections","Map","slug","schema","searchableFields","length","set","tools","maxFileSize","mediaUpload","collectionSlug","schedulePublish","push","mcpGlobals","excludeGlobalSlugs","Set","globals","global","has","enabled","description","withMcp","userCollection","mcp","overrideAuth","req","getDefault","settings","user"],"mappings":"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"}
|
package/dist/introspection.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* True if the collection has Payload drafts enabled in its versions config.
|
|
1
|
+
/**
|
|
2
|
+
* True if the collection has Payload drafts enabled in its versions config.
|
|
3
3
|
*/ export function hasCollectionDrafts(collection) {
|
|
4
4
|
const versions = collection.versions;
|
|
5
5
|
return typeof versions === 'object' && versions !== null && 'drafts' in versions && Boolean(versions.drafts);
|
|
6
6
|
}
|
|
7
|
-
/**
|
|
8
|
-
* Introspect a Payload collection config into structured metadata.
|
|
7
|
+
/**
|
|
8
|
+
* Introspect a Payload collection config into structured metadata.
|
|
9
9
|
*/ export function introspectCollection(collection) {
|
|
10
10
|
const fields = extractFields(collection.fields);
|
|
11
11
|
const relationships = extractRelationships(collection.fields);
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
searchableFields
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
/**
|
|
31
|
-
* Introspect all collections into a map keyed by slug.
|
|
30
|
+
/**
|
|
31
|
+
* Introspect all collections into a map keyed by slug.
|
|
32
32
|
*/ export function introspectCollections(collections) {
|
|
33
33
|
const map = new Map();
|
|
34
34
|
for (const collection of collections){
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
}
|
|
37
37
|
return map;
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Build a flat catalog of every block in the schema. Whether a block can
|
|
41
|
-
* nest other blocks is represented separately in the BlockNestingMap, not
|
|
42
|
-
* as a section/leaf classification — the AI reads both and composes
|
|
43
|
-
* arbitrarily-nested layouts from there.
|
|
39
|
+
/**
|
|
40
|
+
* Build a flat catalog of every block in the schema. Whether a block can
|
|
41
|
+
* nest other blocks is represented separately in the BlockNestingMap, not
|
|
42
|
+
* as a section/leaf classification — the AI reads both and composes
|
|
43
|
+
* arbitrarily-nested layouts from there.
|
|
44
44
|
*/ export function introspectBlocks(blocks) {
|
|
45
45
|
const catalog = blocks.map((block)=>({
|
|
46
46
|
slug: block.slug,
|
|
@@ -50,13 +50,13 @@
|
|
|
50
50
|
blocks: catalog
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
/**
|
|
54
|
-
* Walk every collection and every block, recording each `blocks`-typed
|
|
55
|
-
* field's owner, dotted path, and accepted slugs.
|
|
56
|
-
*
|
|
57
|
-
* The AI uses this to compose layouts at any depth: it looks up which
|
|
58
|
-
* slugs the relevant field accepts, picks one, then if that block has
|
|
59
|
-
* its own `blocks` fields it recurses against the same map.
|
|
53
|
+
/**
|
|
54
|
+
* Walk every collection and every block, recording each `blocks`-typed
|
|
55
|
+
* field's owner, dotted path, and accepted slugs.
|
|
56
|
+
*
|
|
57
|
+
* The AI uses this to compose layouts at any depth: it looks up which
|
|
58
|
+
* slugs the relevant field accepts, picks one, then if that block has
|
|
59
|
+
* its own `blocks` fields it recurses against the same map.
|
|
60
60
|
*/ export function buildBlockNestingMap(collections, blocks) {
|
|
61
61
|
const knownSlugs = new Set(blocks.map((b)=>b.slug));
|
|
62
62
|
const edges = [];
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
}
|
|
79
79
|
return edges;
|
|
80
80
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Build a relationship graph from introspected collection schemas.
|
|
81
|
+
/**
|
|
82
|
+
* Build a relationship graph from introspected collection schemas.
|
|
83
83
|
*/ export function buildRelationshipGraph(schemas) {
|
|
84
84
|
const edges = [];
|
|
85
85
|
for (const [slug, schema] of schemas){
|
|
@@ -95,9 +95,9 @@
|
|
|
95
95
|
return edges;
|
|
96
96
|
}
|
|
97
97
|
// ─── Internal helpers ──────────────────────────────────────────────
|
|
98
|
-
/**
|
|
99
|
-
* Recursively extract field metadata from a Payload fields array.
|
|
100
|
-
* Handles tabs, groups, arrays, rows, and collapsible containers.
|
|
98
|
+
/**
|
|
99
|
+
* Recursively extract field metadata from a Payload fields array.
|
|
100
|
+
* Handles tabs, groups, arrays, rows, and collapsible containers.
|
|
101
101
|
*/ function extractFields(fields) {
|
|
102
102
|
const result = [];
|
|
103
103
|
for (const field of fields){
|
|
@@ -145,8 +145,8 @@
|
|
|
145
145
|
}
|
|
146
146
|
return result;
|
|
147
147
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Extract relationship metadata from fields (for the relationship graph).
|
|
148
|
+
/**
|
|
149
|
+
* Extract relationship metadata from fields (for the relationship graph).
|
|
150
150
|
*/ function extractRelationships(fields, prefix = '') {
|
|
151
151
|
const rels = [];
|
|
152
152
|
for (const field of fields){
|
|
@@ -187,10 +187,10 @@
|
|
|
187
187
|
}
|
|
188
188
|
return rels;
|
|
189
189
|
}
|
|
190
|
-
/**
|
|
191
|
-
* Walk fields recording every `blocks`-typed field encountered, including
|
|
192
|
-
* those nested in tabs/rows/groups/arrays/collapsibles. Each entry carries
|
|
193
|
-
* the dotted path from the owner root to the field.
|
|
190
|
+
/**
|
|
191
|
+
* Walk fields recording every `blocks`-typed field encountered, including
|
|
192
|
+
* those nested in tabs/rows/groups/arrays/collapsibles. Each entry carries
|
|
193
|
+
* the dotted path from the owner root to the field.
|
|
194
194
|
*/ function collectBlocksFieldEdges(fields, ctx) {
|
|
195
195
|
const edges = [];
|
|
196
196
|
for (const field of fields){
|
|
@@ -249,10 +249,10 @@
|
|
|
249
249
|
}
|
|
250
250
|
return edges;
|
|
251
251
|
}
|
|
252
|
-
/**
|
|
253
|
-
* Read block slugs from a blocks-typed field, handling both resolved
|
|
254
|
-
* (field.blocks contains objects) and unresolved (field.blockReferences
|
|
255
|
-
* holds slug strings) forms.
|
|
252
|
+
/**
|
|
253
|
+
* Read block slugs from a blocks-typed field, handling both resolved
|
|
254
|
+
* (field.blocks contains objects) and unresolved (field.blockReferences
|
|
255
|
+
* holds slug strings) forms.
|
|
256
256
|
*/ function readBlockSlugs(field) {
|
|
257
257
|
const f = field;
|
|
258
258
|
if (Array.isArray(f.blocks) && f.blocks.length > 0 && typeof f.blocks[0] === 'object' && f.blocks[0]?.slug) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/introspection.ts"],"sourcesContent":["import type { Block, CollectionConfig, Field } from 'payload'\r\nimport type {\r\n BlockCatalog,\r\n BlockNestingMap,\r\n BlockSchema,\r\n CollectionSchema,\r\n FieldSchema,\r\n RelationshipEdge,\r\n} from './types'\r\n\r\n/**\r\n * True if the collection has Payload drafts enabled in its versions config.\r\n */\r\nexport function hasCollectionDrafts(collection: CollectionConfig): boolean {\r\n const versions = collection.versions\r\n return (\r\n typeof versions === 'object' &&\r\n versions !== null &&\r\n 'drafts' in versions &&\r\n Boolean(versions.drafts)\r\n )\r\n}\r\n\r\n/**\r\n * Introspect a Payload collection config into structured metadata.\r\n */\r\nexport function introspectCollection(collection: CollectionConfig): CollectionSchema {\r\n const fields = extractFields(collection.fields)\r\n const relationships = extractRelationships(collection.fields)\r\n const searchableFields = fields\r\n .filter((f) => ['text', 'email'].includes(f.type) && ['name', 'title', 'slug'].includes(f.name))\r\n .map((f) => f.name)\r\n\r\n const hasLivePreview = !!(\r\n collection.admin &&\r\n typeof collection.admin === 'object' &&\r\n 'livePreview' in collection.admin &&\r\n collection.admin.livePreview\r\n )\r\n\r\n return {\r\n slug: collection.slug,\r\n fields,\r\n hasDrafts: hasCollectionDrafts(collection),\r\n hasLivePreview,\r\n relationships,\r\n searchableFields,\r\n }\r\n}\r\n\r\n/**\r\n * Introspect all collections into a map keyed by slug.\r\n */\r\nexport function introspectCollections(\r\n collections: CollectionConfig[],\r\n): Map<string, CollectionSchema> {\r\n const map = new Map<string, CollectionSchema>()\r\n for (const collection of collections) {\r\n map.set(collection.slug, introspectCollection(collection))\r\n }\r\n return map\r\n}\r\n\r\n/**\r\n * Build a flat catalog of every block in the schema. Whether a block can\r\n * nest other blocks is represented separately in the BlockNestingMap, not\r\n * as a section/leaf classification — the AI reads both and composes\r\n * arbitrarily-nested layouts from there.\r\n */\r\nexport function introspectBlocks(blocks: Block[]): BlockCatalog {\r\n const catalog: BlockSchema[] = blocks.map((block) => ({\r\n slug: block.slug,\r\n fields: extractFields(block.fields),\r\n }))\r\n return { blocks: catalog }\r\n}\r\n\r\n/**\r\n * Walk every collection and every block, recording each `blocks`-typed\r\n * field's owner, dotted path, and accepted slugs.\r\n *\r\n * The AI uses this to compose layouts at any depth: it looks up which\r\n * slugs the relevant field accepts, picks one, then if that block has\r\n * its own `blocks` fields it recurses against the same map.\r\n */\r\nexport function buildBlockNestingMap(\r\n collections: CollectionConfig[],\r\n blocks: Block[],\r\n): BlockNestingMap {\r\n const knownSlugs = new Set(blocks.map((b) => b.slug))\r\n const edges: BlockNestingMap = []\r\n\r\n for (const collection of collections) {\r\n edges.push(\r\n ...collectBlocksFieldEdges(collection.fields, {\r\n owner: collection.slug,\r\n ownerType: 'collection',\r\n prefix: '',\r\n knownSlugs,\r\n }),\r\n )\r\n }\r\n\r\n for (const block of blocks) {\r\n edges.push(\r\n ...collectBlocksFieldEdges(block.fields, {\r\n owner: block.slug,\r\n ownerType: 'block',\r\n prefix: '',\r\n knownSlugs,\r\n }),\r\n )\r\n }\r\n\r\n return edges\r\n}\r\n\r\n/**\r\n * Build a relationship graph from introspected collection schemas.\r\n */\r\nexport function buildRelationshipGraph(\r\n schemas: Map<string, CollectionSchema>,\r\n): RelationshipEdge[] {\r\n const edges: RelationshipEdge[] = []\r\n for (const [slug, schema] of schemas) {\r\n for (const rel of schema.relationships) {\r\n edges.push({\r\n fromCollection: slug,\r\n fieldName: rel.fieldName,\r\n toCollection: rel.relationTo,\r\n hasMany: rel.hasMany,\r\n })\r\n }\r\n }\r\n return edges\r\n}\r\n\r\n// ─── Internal helpers ──────────────────────────────────────────────\r\n\r\n/**\r\n * Recursively extract field metadata from a Payload fields array.\r\n * Handles tabs, groups, arrays, rows, and collapsible containers.\r\n */\r\nfunction extractFields(fields: Field[]): FieldSchema[] {\r\n const result: FieldSchema[] = []\r\n\r\n for (const field of fields) {\r\n if ('name' in field && field.name) {\r\n const schema: FieldSchema = {\r\n name: field.name,\r\n type: field.type,\r\n }\r\n\r\n if ('required' in field && field.required) schema.required = true\r\n if ('hasMany' in field && field.hasMany) schema.hasMany = true\r\n if ('relationTo' in field && field.relationTo) {\r\n schema.relationTo = field.relationTo as string | string[]\r\n }\r\n if ('maxRows' in field && field.maxRows) schema.maxRows = field.maxRows\r\n\r\n if (field.type === 'select' && 'options' in field && Array.isArray(field.options)) {\r\n schema.options = field.options.map((opt) =>\r\n typeof opt === 'string'\r\n ? { label: opt, value: opt }\r\n : { label: String(opt.label), value: String(opt.value) },\r\n )\r\n }\r\n\r\n if (field.type === 'array' && 'fields' in field) {\r\n schema.fields = extractFields(field.fields)\r\n }\r\n if (field.type === 'group' && 'fields' in field) {\r\n schema.fields = extractFields(field.fields)\r\n }\r\n\r\n result.push(schema)\r\n }\r\n\r\n if (field.type === 'tabs' && 'tabs' in field) {\r\n for (const tab of field.tabs) {\r\n if ('fields' in tab) {\r\n result.push(...extractFields(tab.fields))\r\n }\r\n }\r\n }\r\n if (field.type === 'row' && 'fields' in field) {\r\n result.push(...extractFields(field.fields))\r\n }\r\n if (field.type === 'collapsible' && 'fields' in field) {\r\n result.push(...extractFields(field.fields))\r\n }\r\n }\r\n\r\n return result\r\n}\r\n\r\n/**\r\n * Extract relationship metadata from fields (for the relationship graph).\r\n */\r\nfunction extractRelationships(\r\n fields: Field[],\r\n prefix = '',\r\n): Array<{ fieldName: string; relationTo: string | string[]; hasMany: boolean }> {\r\n const rels: Array<{ fieldName: string; relationTo: string | string[]; hasMany: boolean }> = []\r\n\r\n for (const field of fields) {\r\n const fieldName = 'name' in field && field.name ? `${prefix}${field.name}` : prefix\r\n\r\n if (field.type === 'relationship' && 'relationTo' in field) {\r\n rels.push({\r\n fieldName,\r\n relationTo: field.relationTo as string | string[],\r\n hasMany: !!('hasMany' in field && field.hasMany),\r\n })\r\n }\r\n\r\n if (field.type === 'upload' && 'relationTo' in field) {\r\n rels.push({\r\n fieldName,\r\n relationTo: field.relationTo as string,\r\n hasMany: !!('hasMany' in field && field.hasMany),\r\n })\r\n }\r\n\r\n if (field.type === 'tabs' && 'tabs' in field) {\r\n for (const tab of field.tabs) {\r\n if ('fields' in tab) {\r\n rels.push(...extractRelationships(tab.fields, prefix))\r\n }\r\n }\r\n }\r\n if (field.type === 'group' && 'fields' in field) {\r\n rels.push(...extractRelationships(field.fields, `${fieldName}.`))\r\n }\r\n if (field.type === 'array' && 'fields' in field) {\r\n rels.push(...extractRelationships(field.fields, `${fieldName}[].`))\r\n }\r\n if (field.type === 'row' && 'fields' in field) {\r\n rels.push(...extractRelationships(field.fields, prefix))\r\n }\r\n if (field.type === 'collapsible' && 'fields' in field) {\r\n rels.push(...extractRelationships(field.fields, prefix))\r\n }\r\n }\r\n\r\n return rels\r\n}\r\n\r\ninterface NestingScanContext {\r\n owner: string\r\n ownerType: 'collection' | 'block'\r\n prefix: string\r\n knownSlugs: Set<string>\r\n}\r\n\r\n/**\r\n * Walk fields recording every `blocks`-typed field encountered, including\r\n * those nested in tabs/rows/groups/arrays/collapsibles. Each entry carries\r\n * the dotted path from the owner root to the field.\r\n */\r\nfunction collectBlocksFieldEdges(fields: Field[], ctx: NestingScanContext): BlockNestingMap {\r\n const edges: BlockNestingMap = []\r\n\r\n for (const field of fields) {\r\n if (field.type === 'blocks') {\r\n const fieldName = 'name' in field && field.name ? field.name : ''\r\n const fullPath = ctx.prefix ? `${ctx.prefix}.${fieldName}` : fieldName\r\n const allSlugs = readBlockSlugs(field as Field & { type: 'blocks' })\r\n const acceptedSlugs = allSlugs.filter((s) => ctx.knownSlugs.has(s))\r\n\r\n const edge: BlockNestingMap[number] = {\r\n owner: ctx.owner,\r\n ownerType: ctx.ownerType,\r\n fieldPath: fullPath,\r\n acceptedBlockSlugs: acceptedSlugs,\r\n }\r\n const maxRows = (field as Field & { type: 'blocks' }).maxRows\r\n if (typeof maxRows === 'number') edge.maxRows = maxRows\r\n edges.push(edge)\r\n continue\r\n }\r\n\r\n if (field.type === 'tabs' && 'tabs' in field) {\r\n for (const tab of field.tabs) {\r\n if (!('fields' in tab)) continue\r\n const tabName = 'name' in tab && tab.name ? tab.name : ''\r\n const tabPrefix = tabName\r\n ? ctx.prefix\r\n ? `${ctx.prefix}.${tabName}`\r\n : tabName\r\n : ctx.prefix\r\n edges.push(...collectBlocksFieldEdges(tab.fields, { ...ctx, prefix: tabPrefix }))\r\n }\r\n continue\r\n }\r\n if (field.type === 'row' && 'fields' in field) {\r\n edges.push(...collectBlocksFieldEdges(field.fields, ctx))\r\n continue\r\n }\r\n if (field.type === 'collapsible' && 'fields' in field) {\r\n edges.push(...collectBlocksFieldEdges(field.fields, ctx))\r\n continue\r\n }\r\n if (field.type === 'group' && 'fields' in field && 'name' in field && field.name) {\r\n const newPrefix = ctx.prefix ? `${ctx.prefix}.${field.name}` : field.name\r\n edges.push(...collectBlocksFieldEdges(field.fields, { ...ctx, prefix: newPrefix }))\r\n continue\r\n }\r\n if (field.type === 'array' && 'fields' in field && 'name' in field && field.name) {\r\n const newPrefix = ctx.prefix ? `${ctx.prefix}.${field.name}[]` : `${field.name}[]`\r\n edges.push(...collectBlocksFieldEdges(field.fields, { ...ctx, prefix: newPrefix }))\r\n continue\r\n }\r\n }\r\n\r\n return edges\r\n}\r\n\r\n/**\r\n * Read block slugs from a blocks-typed field, handling both resolved\r\n * (field.blocks contains objects) and unresolved (field.blockReferences\r\n * holds slug strings) forms.\r\n */\r\nfunction readBlockSlugs(field: Field & { type: 'blocks' }): string[] {\r\n const f = field as any\r\n\r\n if (\r\n Array.isArray(f.blocks) &&\r\n f.blocks.length > 0 &&\r\n typeof f.blocks[0] === 'object' &&\r\n f.blocks[0]?.slug\r\n ) {\r\n return f.blocks.map((b: { slug: string }) => b.slug)\r\n }\r\n\r\n if (Array.isArray(f.blockReferences) && f.blockReferences.length > 0) {\r\n return f.blockReferences.filter((ref: unknown) => typeof ref === 'string') as string[]\r\n }\r\n\r\n return []\r\n}\r\n"],"names":["hasCollectionDrafts","collection","versions","Boolean","drafts","introspectCollection","fields","extractFields","relationships","extractRelationships","searchableFields","filter","f","includes","type","name","map","hasLivePreview","admin","livePreview","slug","hasDrafts","introspectCollections","collections","Map","set","introspectBlocks","blocks","catalog","block","buildBlockNestingMap","knownSlugs","Set","b","edges","push","collectBlocksFieldEdges","owner","ownerType","prefix","buildRelationshipGraph","schemas","schema","rel","fromCollection","fieldName","toCollection","relationTo","hasMany","result","field","required","maxRows","Array","isArray","options","opt","label","value","String","tab","tabs","rels","ctx","fullPath","allSlugs","readBlockSlugs","acceptedSlugs","s","has","edge","fieldPath","acceptedBlockSlugs","tabName","tabPrefix","newPrefix","length","blockReferences","ref"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/introspection.ts"],"sourcesContent":["import type { Block, CollectionConfig, Field } from 'payload'\nimport type {\n BlockCatalog,\n BlockNestingMap,\n BlockSchema,\n CollectionSchema,\n FieldSchema,\n RelationshipEdge,\n} from './types'\n\n/**\n * True if the collection has Payload drafts enabled in its versions config.\n */\nexport function hasCollectionDrafts(collection: CollectionConfig): boolean {\n const versions = collection.versions\n return (\n typeof versions === 'object' &&\n versions !== null &&\n 'drafts' in versions &&\n Boolean(versions.drafts)\n )\n}\n\n/**\n * Introspect a Payload collection config into structured metadata.\n */\nexport function introspectCollection(collection: CollectionConfig): CollectionSchema {\n const fields = extractFields(collection.fields)\n const relationships = extractRelationships(collection.fields)\n const searchableFields = fields\n .filter((f) => ['text', 'email'].includes(f.type) && ['name', 'title', 'slug'].includes(f.name))\n .map((f) => f.name)\n\n const hasLivePreview = !!(\n collection.admin &&\n typeof collection.admin === 'object' &&\n 'livePreview' in collection.admin &&\n collection.admin.livePreview\n )\n\n return {\n slug: collection.slug,\n fields,\n hasDrafts: hasCollectionDrafts(collection),\n hasLivePreview,\n relationships,\n searchableFields,\n }\n}\n\n/**\n * Introspect all collections into a map keyed by slug.\n */\nexport function introspectCollections(\n collections: CollectionConfig[],\n): Map<string, CollectionSchema> {\n const map = new Map<string, CollectionSchema>()\n for (const collection of collections) {\n map.set(collection.slug, introspectCollection(collection))\n }\n return map\n}\n\n/**\n * Build a flat catalog of every block in the schema. Whether a block can\n * nest other blocks is represented separately in the BlockNestingMap, not\n * as a section/leaf classification — the AI reads both and composes\n * arbitrarily-nested layouts from there.\n */\nexport function introspectBlocks(blocks: Block[]): BlockCatalog {\n const catalog: BlockSchema[] = blocks.map((block) => ({\n slug: block.slug,\n fields: extractFields(block.fields),\n }))\n return { blocks: catalog }\n}\n\n/**\n * Walk every collection and every block, recording each `blocks`-typed\n * field's owner, dotted path, and accepted slugs.\n *\n * The AI uses this to compose layouts at any depth: it looks up which\n * slugs the relevant field accepts, picks one, then if that block has\n * its own `blocks` fields it recurses against the same map.\n */\nexport function buildBlockNestingMap(\n collections: CollectionConfig[],\n blocks: Block[],\n): BlockNestingMap {\n const knownSlugs = new Set(blocks.map((b) => b.slug))\n const edges: BlockNestingMap = []\n\n for (const collection of collections) {\n edges.push(\n ...collectBlocksFieldEdges(collection.fields, {\n owner: collection.slug,\n ownerType: 'collection',\n prefix: '',\n knownSlugs,\n }),\n )\n }\n\n for (const block of blocks) {\n edges.push(\n ...collectBlocksFieldEdges(block.fields, {\n owner: block.slug,\n ownerType: 'block',\n prefix: '',\n knownSlugs,\n }),\n )\n }\n\n return edges\n}\n\n/**\n * Build a relationship graph from introspected collection schemas.\n */\nexport function buildRelationshipGraph(\n schemas: Map<string, CollectionSchema>,\n): RelationshipEdge[] {\n const edges: RelationshipEdge[] = []\n for (const [slug, schema] of schemas) {\n for (const rel of schema.relationships) {\n edges.push({\n fromCollection: slug,\n fieldName: rel.fieldName,\n toCollection: rel.relationTo,\n hasMany: rel.hasMany,\n })\n }\n }\n return edges\n}\n\n// ─── Internal helpers ──────────────────────────────────────────────\n\n/**\n * Recursively extract field metadata from a Payload fields array.\n * Handles tabs, groups, arrays, rows, and collapsible containers.\n */\nfunction extractFields(fields: Field[]): FieldSchema[] {\n const result: FieldSchema[] = []\n\n for (const field of fields) {\n if ('name' in field && field.name) {\n const schema: FieldSchema = {\n name: field.name,\n type: field.type,\n }\n\n if ('required' in field && field.required) schema.required = true\n if ('hasMany' in field && field.hasMany) schema.hasMany = true\n if ('relationTo' in field && field.relationTo) {\n schema.relationTo = field.relationTo as string | string[]\n }\n if ('maxRows' in field && field.maxRows) schema.maxRows = field.maxRows\n\n if (field.type === 'select' && 'options' in field && Array.isArray(field.options)) {\n schema.options = field.options.map((opt) =>\n typeof opt === 'string'\n ? { label: opt, value: opt }\n : { label: String(opt.label), value: String(opt.value) },\n )\n }\n\n if (field.type === 'array' && 'fields' in field) {\n schema.fields = extractFields(field.fields)\n }\n if (field.type === 'group' && 'fields' in field) {\n schema.fields = extractFields(field.fields)\n }\n\n result.push(schema)\n }\n\n if (field.type === 'tabs' && 'tabs' in field) {\n for (const tab of field.tabs) {\n if ('fields' in tab) {\n result.push(...extractFields(tab.fields))\n }\n }\n }\n if (field.type === 'row' && 'fields' in field) {\n result.push(...extractFields(field.fields))\n }\n if (field.type === 'collapsible' && 'fields' in field) {\n result.push(...extractFields(field.fields))\n }\n }\n\n return result\n}\n\n/**\n * Extract relationship metadata from fields (for the relationship graph).\n */\nfunction extractRelationships(\n fields: Field[],\n prefix = '',\n): Array<{ fieldName: string; relationTo: string | string[]; hasMany: boolean }> {\n const rels: Array<{ fieldName: string; relationTo: string | string[]; hasMany: boolean }> = []\n\n for (const field of fields) {\n const fieldName = 'name' in field && field.name ? `${prefix}${field.name}` : prefix\n\n if (field.type === 'relationship' && 'relationTo' in field) {\n rels.push({\n fieldName,\n relationTo: field.relationTo as string | string[],\n hasMany: !!('hasMany' in field && field.hasMany),\n })\n }\n\n if (field.type === 'upload' && 'relationTo' in field) {\n rels.push({\n fieldName,\n relationTo: field.relationTo as string,\n hasMany: !!('hasMany' in field && field.hasMany),\n })\n }\n\n if (field.type === 'tabs' && 'tabs' in field) {\n for (const tab of field.tabs) {\n if ('fields' in tab) {\n rels.push(...extractRelationships(tab.fields, prefix))\n }\n }\n }\n if (field.type === 'group' && 'fields' in field) {\n rels.push(...extractRelationships(field.fields, `${fieldName}.`))\n }\n if (field.type === 'array' && 'fields' in field) {\n rels.push(...extractRelationships(field.fields, `${fieldName}[].`))\n }\n if (field.type === 'row' && 'fields' in field) {\n rels.push(...extractRelationships(field.fields, prefix))\n }\n if (field.type === 'collapsible' && 'fields' in field) {\n rels.push(...extractRelationships(field.fields, prefix))\n }\n }\n\n return rels\n}\n\ninterface NestingScanContext {\n owner: string\n ownerType: 'collection' | 'block'\n prefix: string\n knownSlugs: Set<string>\n}\n\n/**\n * Walk fields recording every `blocks`-typed field encountered, including\n * those nested in tabs/rows/groups/arrays/collapsibles. Each entry carries\n * the dotted path from the owner root to the field.\n */\nfunction collectBlocksFieldEdges(fields: Field[], ctx: NestingScanContext): BlockNestingMap {\n const edges: BlockNestingMap = []\n\n for (const field of fields) {\n if (field.type === 'blocks') {\n const fieldName = 'name' in field && field.name ? field.name : ''\n const fullPath = ctx.prefix ? `${ctx.prefix}.${fieldName}` : fieldName\n const allSlugs = readBlockSlugs(field as Field & { type: 'blocks' })\n const acceptedSlugs = allSlugs.filter((s) => ctx.knownSlugs.has(s))\n\n const edge: BlockNestingMap[number] = {\n owner: ctx.owner,\n ownerType: ctx.ownerType,\n fieldPath: fullPath,\n acceptedBlockSlugs: acceptedSlugs,\n }\n const maxRows = (field as Field & { type: 'blocks' }).maxRows\n if (typeof maxRows === 'number') edge.maxRows = maxRows\n edges.push(edge)\n continue\n }\n\n if (field.type === 'tabs' && 'tabs' in field) {\n for (const tab of field.tabs) {\n if (!('fields' in tab)) continue\n const tabName = 'name' in tab && tab.name ? tab.name : ''\n const tabPrefix = tabName\n ? ctx.prefix\n ? `${ctx.prefix}.${tabName}`\n : tabName\n : ctx.prefix\n edges.push(...collectBlocksFieldEdges(tab.fields, { ...ctx, prefix: tabPrefix }))\n }\n continue\n }\n if (field.type === 'row' && 'fields' in field) {\n edges.push(...collectBlocksFieldEdges(field.fields, ctx))\n continue\n }\n if (field.type === 'collapsible' && 'fields' in field) {\n edges.push(...collectBlocksFieldEdges(field.fields, ctx))\n continue\n }\n if (field.type === 'group' && 'fields' in field && 'name' in field && field.name) {\n const newPrefix = ctx.prefix ? `${ctx.prefix}.${field.name}` : field.name\n edges.push(...collectBlocksFieldEdges(field.fields, { ...ctx, prefix: newPrefix }))\n continue\n }\n if (field.type === 'array' && 'fields' in field && 'name' in field && field.name) {\n const newPrefix = ctx.prefix ? `${ctx.prefix}.${field.name}[]` : `${field.name}[]`\n edges.push(...collectBlocksFieldEdges(field.fields, { ...ctx, prefix: newPrefix }))\n continue\n }\n }\n\n return edges\n}\n\n/**\n * Read block slugs from a blocks-typed field, handling both resolved\n * (field.blocks contains objects) and unresolved (field.blockReferences\n * holds slug strings) forms.\n */\nfunction readBlockSlugs(field: Field & { type: 'blocks' }): string[] {\n const f = field as any\n\n if (\n Array.isArray(f.blocks) &&\n f.blocks.length > 0 &&\n typeof f.blocks[0] === 'object' &&\n f.blocks[0]?.slug\n ) {\n return f.blocks.map((b: { slug: string }) => b.slug)\n }\n\n if (Array.isArray(f.blockReferences) && f.blockReferences.length > 0) {\n return f.blockReferences.filter((ref: unknown) => typeof ref === 'string') as string[]\n }\n\n return []\n}\n"],"names":["hasCollectionDrafts","collection","versions","Boolean","drafts","introspectCollection","fields","extractFields","relationships","extractRelationships","searchableFields","filter","f","includes","type","name","map","hasLivePreview","admin","livePreview","slug","hasDrafts","introspectCollections","collections","Map","set","introspectBlocks","blocks","catalog","block","buildBlockNestingMap","knownSlugs","Set","b","edges","push","collectBlocksFieldEdges","owner","ownerType","prefix","buildRelationshipGraph","schemas","schema","rel","fromCollection","fieldName","toCollection","relationTo","hasMany","result","field","required","maxRows","Array","isArray","options","opt","label","value","String","tab","tabs","rels","ctx","fullPath","allSlugs","readBlockSlugs","acceptedSlugs","s","has","edge","fieldPath","acceptedBlockSlugs","tabName","tabPrefix","newPrefix","length","blockReferences","ref"],"mappings":"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"}
|
package/dist/prompts.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate MCP prompts that teach the AI about the content model.
|
|
3
|
-
*
|
|
4
|
-
* Auto-generates content model overview, block composition guide, and
|
|
5
|
-
* draft workflow guide. User-supplied domain prompts are appended.
|
|
1
|
+
/**
|
|
2
|
+
* Generate MCP prompts that teach the AI about the content model.
|
|
3
|
+
*
|
|
4
|
+
* Auto-generates content model overview, block composition guide, and
|
|
5
|
+
* draft workflow guide. User-supplied domain prompts are appended.
|
|
6
6
|
*/ export function generatePrompts(schemas, catalog, nesting, relationships, domainPrompts) {
|
|
7
7
|
const prompts = [
|
|
8
8
|
buildContentModelOverview(schemas, relationships),
|
package/dist/prompts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/prompts.ts"],"sourcesContent":["import type {\r\n BlockCatalog,\r\n BlockNestingMap,\r\n CollectionSchema,\r\n DomainPrompt,\r\n RelationshipEdge,\r\n} from './types'\r\n\r\n/**\r\n * Generate MCP prompts that teach the AI about the content model.\r\n *\r\n * Auto-generates content model overview, block composition guide, and\r\n * draft workflow guide. User-supplied domain prompts are appended.\r\n */\r\nexport function generatePrompts(\r\n schemas: Map<string, CollectionSchema>,\r\n catalog: BlockCatalog,\r\n nesting: BlockNestingMap,\r\n relationships: RelationshipEdge[],\r\n domainPrompts?: DomainPrompt[],\r\n) {\r\n const prompts = [\r\n buildContentModelOverview(schemas, relationships),\r\n buildBlockCompositionGuide(catalog, nesting),\r\n buildDraftWorkflowGuide(schemas),\r\n ]\r\n\r\n if (domainPrompts?.length) {\r\n for (const dp of domainPrompts) {\r\n prompts.push({\r\n name: dp.name,\r\n title: dp.title,\r\n description: dp.description,\r\n handler() {\r\n return {\r\n messages: [\r\n {\r\n content: { type: 'text' as const, text: dp.content },\r\n role: 'user' as const,\r\n },\r\n ],\r\n }\r\n },\r\n })\r\n }\r\n }\r\n\r\n return prompts\r\n}\r\n\r\n// ─── Prompt builders ──────────────────────────────────────────────\r\n\r\nfunction buildContentModelOverview(\r\n schemas: Map<string, CollectionSchema>,\r\n relationships: RelationshipEdge[],\r\n) {\r\n return {\r\n name: 'contentModelOverview',\r\n title: 'Content Model Overview',\r\n description:\r\n 'Describes every collection in the CMS — its purpose, fields, and relationships to other collections.',\r\n handler() {\r\n const lines: string[] = ['# Content Model Overview', '']\r\n\r\n for (const [slug, schema] of schemas) {\r\n lines.push(`## Collection: ${slug}`)\r\n lines.push(`Draft support: ${schema.hasDrafts ? 'yes' : 'no'}`)\r\n lines.push(`Live preview: ${schema.hasLivePreview ? 'yes' : 'no'}`)\r\n lines.push('')\r\n\r\n lines.push('### Fields')\r\n for (const field of schema.fields) {\r\n const parts = [`- **${field.name}** (${field.type})`]\r\n if (field.required) parts.push(' *required*')\r\n if (field.hasMany) parts.push(' hasMany')\r\n if (field.relationTo) {\r\n const targets = Array.isArray(field.relationTo)\r\n ? field.relationTo.join(', ')\r\n : field.relationTo\r\n parts.push(` → ${targets}`)\r\n }\r\n if (field.options?.length) {\r\n const vals = field.options.map((o) => o.value).join(', ')\r\n parts.push(` [${vals}]`)\r\n }\r\n if (field.maxRows) parts.push(` maxRows: ${field.maxRows}`)\r\n lines.push(parts.join(''))\r\n }\r\n lines.push('')\r\n\r\n const collRels = relationships.filter((r) => r.fromCollection === slug)\r\n if (collRels.length > 0) {\r\n lines.push('### Relationships')\r\n for (const rel of collRels) {\r\n const targets = Array.isArray(rel.toCollection)\r\n ? rel.toCollection.join(', ')\r\n : rel.toCollection\r\n lines.push(`- ${rel.fieldName} → ${targets}${rel.hasMany ? ' (hasMany)' : ''}`)\r\n }\r\n lines.push('')\r\n }\r\n }\r\n\r\n return {\r\n messages: [\r\n {\r\n content: { type: 'text' as const, text: lines.join('\\n') },\r\n role: 'user' as const,\r\n },\r\n ],\r\n }\r\n },\r\n }\r\n}\r\n\r\nfunction buildBlockCompositionGuide(catalog: BlockCatalog, nesting: BlockNestingMap) {\r\n return {\r\n name: 'blockCompositionGuide',\r\n title: 'Block Composition Guide',\r\n description:\r\n 'Lists every block type, its fields, and which slugs each blocks-typed field accepts. Use this with the relationshipGraph and collectionSchema resources to compose layouts at any depth.',\r\n handler() {\r\n const lines: string[] = [\r\n '# Block Composition Guide',\r\n '',\r\n 'Every block has a `blockType` discriminator plus its own fields. A block may include one or more `blocks`-typed fields that nest other blocks. The accepted slugs per field are listed below — recurse into the same map for deeper nesting.',\r\n '',\r\n ]\r\n\r\n lines.push('## Where blocks can nest')\r\n lines.push('')\r\n const collectionEdges = nesting.filter((e) => e.ownerType === 'collection')\r\n const blockEdges = nesting.filter((e) => e.ownerType === 'block')\r\n\r\n if (collectionEdges.length > 0) {\r\n lines.push('### In collections')\r\n for (const edge of collectionEdges) {\r\n const cap = edge.maxRows ? ` (max ${edge.maxRows})` : ''\r\n lines.push(\r\n `- \\`${edge.owner}.${edge.fieldPath}\\`${cap} accepts: ${edge.acceptedBlockSlugs.join(', ') || '(none)'}`,\r\n )\r\n }\r\n lines.push('')\r\n }\r\n\r\n if (blockEdges.length > 0) {\r\n lines.push('### In blocks (nested composition)')\r\n for (const edge of blockEdges) {\r\n const cap = edge.maxRows ? ` (max ${edge.maxRows})` : ''\r\n lines.push(\r\n `- block \\`${edge.owner}\\` field \\`${edge.fieldPath}\\`${cap} accepts: ${edge.acceptedBlockSlugs.join(', ') || '(none)'}`,\r\n )\r\n }\r\n lines.push('')\r\n }\r\n\r\n lines.push('## Block fields')\r\n lines.push('')\r\n for (const block of catalog.blocks) {\r\n lines.push(`### ${block.slug}`)\r\n if (block.fields.length === 0) {\r\n lines.push('(no fields)')\r\n } else {\r\n for (const f of block.fields) {\r\n const parts = [`- ${f.name} (${f.type})`]\r\n if (f.required) parts.push(' *required*')\r\n if (f.options?.length) {\r\n parts.push(` [${f.options.map((o) => o.value).join(', ')}]`)\r\n }\r\n if (f.relationTo) {\r\n const targets = Array.isArray(f.relationTo)\r\n ? f.relationTo.join(', ')\r\n : f.relationTo\r\n parts.push(` → ${targets}`)\r\n }\r\n lines.push(parts.join(''))\r\n }\r\n }\r\n lines.push('')\r\n }\r\n\r\n return {\r\n messages: [\r\n {\r\n content: { type: 'text' as const, text: lines.join('\\n') },\r\n role: 'user' as const,\r\n },\r\n ],\r\n }\r\n },\r\n }\r\n}\r\n\r\nfunction buildDraftWorkflowGuide(schemas: Map<string, CollectionSchema>) {\r\n return {\r\n name: 'draftWorkflowGuide',\r\n title: 'Draft Workflow Guide',\r\n description:\r\n 'Explains which collections support drafts, how to create drafts, review them, and publish.',\r\n handler() {\r\n const draftCollections: string[] = []\r\n const publishCollections: string[] = []\r\n\r\n for (const [slug, schema] of schemas) {\r\n if (schema.hasDrafts) {\r\n draftCollections.push(slug)\r\n } else {\r\n publishCollections.push(slug)\r\n }\r\n }\r\n\r\n const lines: string[] = ['# Draft Workflow Guide', '', '## Collections with draft support', '']\r\n\r\n if (draftCollections.length === 0) {\r\n lines.push('No collections have draft support enabled.')\r\n } else {\r\n for (const slug of draftCollections) {\r\n lines.push(`- **${slug}**`)\r\n }\r\n lines.push('')\r\n lines.push('### How drafts work')\r\n lines.push(\r\n '1. When you create or update a document in a draft-enabled collection, set `_status: \"draft\"` to keep it unpublished.',\r\n )\r\n lines.push(\r\n '2. Draft documents are only visible via preview URLs or the admin panel — they are not public.',\r\n )\r\n lines.push(\r\n '3. To publish a draft, use the `publishDraft` tool (raw `update` is locked on always-draft collections).',\r\n )\r\n lines.push('4. You can review a draft via its preview URL before publishing.')\r\n }\r\n\r\n lines.push('')\r\n lines.push('## Collections without draft support')\r\n lines.push('')\r\n\r\n if (publishCollections.length === 0) {\r\n lines.push('All collections support drafts.')\r\n } else {\r\n for (const slug of publishCollections) {\r\n lines.push(`- **${slug}** — changes are published immediately`)\r\n }\r\n }\r\n\r\n return {\r\n messages: [\r\n {\r\n content: { type: 'text' as const, text: lines.join('\\n') },\r\n role: 'user' as const,\r\n },\r\n ],\r\n }\r\n },\r\n }\r\n}\r\n"],"names":["generatePrompts","schemas","catalog","nesting","relationships","domainPrompts","prompts","buildContentModelOverview","buildBlockCompositionGuide","buildDraftWorkflowGuide","length","dp","push","name","title","description","handler","messages","content","type","text","role","lines","slug","schema","hasDrafts","hasLivePreview","field","fields","parts","required","hasMany","relationTo","targets","Array","isArray","join","options","vals","map","o","value","maxRows","collRels","filter","r","fromCollection","rel","toCollection","fieldName","collectionEdges","e","ownerType","blockEdges","edge","cap","owner","fieldPath","acceptedBlockSlugs","block","blocks","f","draftCollections","publishCollections"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/prompts.ts"],"sourcesContent":["import type {\n BlockCatalog,\n BlockNestingMap,\n CollectionSchema,\n DomainPrompt,\n RelationshipEdge,\n} from './types'\n\n/**\n * Generate MCP prompts that teach the AI about the content model.\n *\n * Auto-generates content model overview, block composition guide, and\n * draft workflow guide. User-supplied domain prompts are appended.\n */\nexport function generatePrompts(\n schemas: Map<string, CollectionSchema>,\n catalog: BlockCatalog,\n nesting: BlockNestingMap,\n relationships: RelationshipEdge[],\n domainPrompts?: DomainPrompt[],\n) {\n const prompts = [\n buildContentModelOverview(schemas, relationships),\n buildBlockCompositionGuide(catalog, nesting),\n buildDraftWorkflowGuide(schemas),\n ]\n\n if (domainPrompts?.length) {\n for (const dp of domainPrompts) {\n prompts.push({\n name: dp.name,\n title: dp.title,\n description: dp.description,\n handler() {\n return {\n messages: [\n {\n content: { type: 'text' as const, text: dp.content },\n role: 'user' as const,\n },\n ],\n }\n },\n })\n }\n }\n\n return prompts\n}\n\n// ─── Prompt builders ──────────────────────────────────────────────\n\nfunction buildContentModelOverview(\n schemas: Map<string, CollectionSchema>,\n relationships: RelationshipEdge[],\n) {\n return {\n name: 'contentModelOverview',\n title: 'Content Model Overview',\n description:\n 'Describes every collection in the CMS — its purpose, fields, and relationships to other collections.',\n handler() {\n const lines: string[] = ['# Content Model Overview', '']\n\n for (const [slug, schema] of schemas) {\n lines.push(`## Collection: ${slug}`)\n lines.push(`Draft support: ${schema.hasDrafts ? 'yes' : 'no'}`)\n lines.push(`Live preview: ${schema.hasLivePreview ? 'yes' : 'no'}`)\n lines.push('')\n\n lines.push('### Fields')\n for (const field of schema.fields) {\n const parts = [`- **${field.name}** (${field.type})`]\n if (field.required) parts.push(' *required*')\n if (field.hasMany) parts.push(' hasMany')\n if (field.relationTo) {\n const targets = Array.isArray(field.relationTo)\n ? field.relationTo.join(', ')\n : field.relationTo\n parts.push(` → ${targets}`)\n }\n if (field.options?.length) {\n const vals = field.options.map((o) => o.value).join(', ')\n parts.push(` [${vals}]`)\n }\n if (field.maxRows) parts.push(` maxRows: ${field.maxRows}`)\n lines.push(parts.join(''))\n }\n lines.push('')\n\n const collRels = relationships.filter((r) => r.fromCollection === slug)\n if (collRels.length > 0) {\n lines.push('### Relationships')\n for (const rel of collRels) {\n const targets = Array.isArray(rel.toCollection)\n ? rel.toCollection.join(', ')\n : rel.toCollection\n lines.push(`- ${rel.fieldName} → ${targets}${rel.hasMany ? ' (hasMany)' : ''}`)\n }\n lines.push('')\n }\n }\n\n return {\n messages: [\n {\n content: { type: 'text' as const, text: lines.join('\\n') },\n role: 'user' as const,\n },\n ],\n }\n },\n }\n}\n\nfunction buildBlockCompositionGuide(catalog: BlockCatalog, nesting: BlockNestingMap) {\n return {\n name: 'blockCompositionGuide',\n title: 'Block Composition Guide',\n description:\n 'Lists every block type, its fields, and which slugs each blocks-typed field accepts. Use this with the relationshipGraph and collectionSchema resources to compose layouts at any depth.',\n handler() {\n const lines: string[] = [\n '# Block Composition Guide',\n '',\n 'Every block has a `blockType` discriminator plus its own fields. A block may include one or more `blocks`-typed fields that nest other blocks. The accepted slugs per field are listed below — recurse into the same map for deeper nesting.',\n '',\n ]\n\n lines.push('## Where blocks can nest')\n lines.push('')\n const collectionEdges = nesting.filter((e) => e.ownerType === 'collection')\n const blockEdges = nesting.filter((e) => e.ownerType === 'block')\n\n if (collectionEdges.length > 0) {\n lines.push('### In collections')\n for (const edge of collectionEdges) {\n const cap = edge.maxRows ? ` (max ${edge.maxRows})` : ''\n lines.push(\n `- \\`${edge.owner}.${edge.fieldPath}\\`${cap} accepts: ${edge.acceptedBlockSlugs.join(', ') || '(none)'}`,\n )\n }\n lines.push('')\n }\n\n if (blockEdges.length > 0) {\n lines.push('### In blocks (nested composition)')\n for (const edge of blockEdges) {\n const cap = edge.maxRows ? ` (max ${edge.maxRows})` : ''\n lines.push(\n `- block \\`${edge.owner}\\` field \\`${edge.fieldPath}\\`${cap} accepts: ${edge.acceptedBlockSlugs.join(', ') || '(none)'}`,\n )\n }\n lines.push('')\n }\n\n lines.push('## Block fields')\n lines.push('')\n for (const block of catalog.blocks) {\n lines.push(`### ${block.slug}`)\n if (block.fields.length === 0) {\n lines.push('(no fields)')\n } else {\n for (const f of block.fields) {\n const parts = [`- ${f.name} (${f.type})`]\n if (f.required) parts.push(' *required*')\n if (f.options?.length) {\n parts.push(` [${f.options.map((o) => o.value).join(', ')}]`)\n }\n if (f.relationTo) {\n const targets = Array.isArray(f.relationTo)\n ? f.relationTo.join(', ')\n : f.relationTo\n parts.push(` → ${targets}`)\n }\n lines.push(parts.join(''))\n }\n }\n lines.push('')\n }\n\n return {\n messages: [\n {\n content: { type: 'text' as const, text: lines.join('\\n') },\n role: 'user' as const,\n },\n ],\n }\n },\n }\n}\n\nfunction buildDraftWorkflowGuide(schemas: Map<string, CollectionSchema>) {\n return {\n name: 'draftWorkflowGuide',\n title: 'Draft Workflow Guide',\n description:\n 'Explains which collections support drafts, how to create drafts, review them, and publish.',\n handler() {\n const draftCollections: string[] = []\n const publishCollections: string[] = []\n\n for (const [slug, schema] of schemas) {\n if (schema.hasDrafts) {\n draftCollections.push(slug)\n } else {\n publishCollections.push(slug)\n }\n }\n\n const lines: string[] = ['# Draft Workflow Guide', '', '## Collections with draft support', '']\n\n if (draftCollections.length === 0) {\n lines.push('No collections have draft support enabled.')\n } else {\n for (const slug of draftCollections) {\n lines.push(`- **${slug}**`)\n }\n lines.push('')\n lines.push('### How drafts work')\n lines.push(\n '1. When you create or update a document in a draft-enabled collection, set `_status: \"draft\"` to keep it unpublished.',\n )\n lines.push(\n '2. Draft documents are only visible via preview URLs or the admin panel — they are not public.',\n )\n lines.push(\n '3. To publish a draft, use the `publishDraft` tool (raw `update` is locked on always-draft collections).',\n )\n lines.push('4. You can review a draft via its preview URL before publishing.')\n }\n\n lines.push('')\n lines.push('## Collections without draft support')\n lines.push('')\n\n if (publishCollections.length === 0) {\n lines.push('All collections support drafts.')\n } else {\n for (const slug of publishCollections) {\n lines.push(`- **${slug}** — changes are published immediately`)\n }\n }\n\n return {\n messages: [\n {\n content: { type: 'text' as const, text: lines.join('\\n') },\n role: 'user' as const,\n },\n ],\n }\n },\n }\n}\n"],"names":["generatePrompts","schemas","catalog","nesting","relationships","domainPrompts","prompts","buildContentModelOverview","buildBlockCompositionGuide","buildDraftWorkflowGuide","length","dp","push","name","title","description","handler","messages","content","type","text","role","lines","slug","schema","hasDrafts","hasLivePreview","field","fields","parts","required","hasMany","relationTo","targets","Array","isArray","join","options","vals","map","o","value","maxRows","collRels","filter","r","fromCollection","rel","toCollection","fieldName","collectionEdges","e","ownerType","blockEdges","edge","cap","owner","fieldPath","acceptedBlockSlugs","block","blocks","f","draftCollections","publishCollections"],"mappings":"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"}
|
package/dist/resources.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate MCP resources that expose the introspected schema as static JSON.
|
|
3
|
-
*
|
|
4
|
-
* Four resources:
|
|
5
|
-
* - blocks://catalog — flat list of every block and its fields
|
|
6
|
-
* - blocks://nesting — per-blocks-field map of which slugs each field accepts
|
|
7
|
-
* - collections://schema — collection field metadata
|
|
8
|
-
* - collections://relationships — collection relationship graph
|
|
1
|
+
/**
|
|
2
|
+
* Generate MCP resources that expose the introspected schema as static JSON.
|
|
3
|
+
*
|
|
4
|
+
* Four resources:
|
|
5
|
+
* - blocks://catalog — flat list of every block and its fields
|
|
6
|
+
* - blocks://nesting — per-blocks-field map of which slugs each field accepts
|
|
7
|
+
* - collections://schema — collection field metadata
|
|
8
|
+
* - collections://relationships — collection relationship graph
|
|
9
9
|
*/ export function generateResources(schemas, catalog, nesting, relationships) {
|
|
10
10
|
return [
|
|
11
11
|
buildJsonResource({
|
package/dist/resources.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/resources.ts"],"sourcesContent":["import type {\
|
|
1
|
+
{"version":3,"sources":["../src/resources.ts"],"sourcesContent":["import type {\n BlockCatalog,\n BlockNestingMap,\n CollectionSchema,\n RelationshipEdge,\n} from './types'\n\n/**\n * Generate MCP resources that expose the introspected schema as static JSON.\n *\n * Four resources:\n * - blocks://catalog — flat list of every block and its fields\n * - blocks://nesting — per-blocks-field map of which slugs each field accepts\n * - collections://schema — collection field metadata\n * - collections://relationships — collection relationship graph\n */\nexport function generateResources(\n schemas: Map<string, CollectionSchema>,\n catalog: BlockCatalog,\n nesting: BlockNestingMap,\n relationships: RelationshipEdge[],\n) {\n return [\n buildJsonResource({\n name: 'blockCatalog',\n title: 'Block Catalog',\n description:\n 'Flat list of every block type and its fields. Pair with the blockNesting resource to know where each block can be placed.',\n uri: 'blocks://catalog',\n payload: catalog,\n }),\n buildJsonResource({\n name: 'blockNesting',\n title: 'Block Nesting Map',\n description:\n 'For every blocks-typed field in the schema (in collections and inside other blocks), lists the block slugs that field accepts. Use this to compose nested layouts at any depth.',\n uri: 'blocks://nesting',\n payload: nesting,\n }),\n buildJsonResource({\n name: 'collectionSchema',\n title: 'Collection Schema',\n description:\n 'JSON schema of all collections — fields, select options, and relationship targets.',\n uri: 'collections://schema',\n payload: Object.fromEntries(schemas),\n }),\n buildJsonResource({\n name: 'relationshipGraph',\n title: 'Relationship Graph',\n description:\n 'JSON representation of the collection relationship graph — which collections link to which.',\n uri: 'collections://relationships',\n payload: relationships,\n }),\n ]\n}\n\nfunction buildJsonResource(args: {\n name: string\n title: string\n description: string\n uri: string\n payload: unknown\n}) {\n const json = JSON.stringify(args.payload, null, 2)\n return {\n name: args.name,\n title: args.title,\n description: args.description,\n uri: args.uri,\n mimeType: 'application/json',\n handler(uri: URL) {\n return {\n contents: [{ uri: uri.href, text: json }],\n }\n },\n }\n}\n\n"],"names":["generateResources","schemas","catalog","nesting","relationships","buildJsonResource","name","title","description","uri","payload","Object","fromEntries","args","json","JSON","stringify","mimeType","handler","contents","href","text"],"mappings":"AAOA;;;;;;;;CAQC,GACD,OAAO,SAASA,kBACdC,OAAsC,EACtCC,OAAqB,EACrBC,OAAwB,EACxBC,aAAiC;IAEjC,OAAO;QACLC,kBAAkB;YAChBC,MAAM;YACNC,OAAO;YACPC,aACE;YACFC,KAAK;YACLC,SAASR;QACX;QACAG,kBAAkB;YAChBC,MAAM;YACNC,OAAO;YACPC,aACE;YACFC,KAAK;YACLC,SAASP;QACX;QACAE,kBAAkB;YAChBC,MAAM;YACNC,OAAO;YACPC,aACE;YACFC,KAAK;YACLC,SAASC,OAAOC,WAAW,CAACX;QAC9B;QACAI,kBAAkB;YAChBC,MAAM;YACNC,OAAO;YACPC,aACE;YACFC,KAAK;YACLC,SAASN;QACX;KACD;AACH;AAEA,SAASC,kBAAkBQ,IAM1B;IACC,MAAMC,OAAOC,KAAKC,SAAS,CAACH,KAAKH,OAAO,EAAE,MAAM;IAChD,OAAO;QACLJ,MAAMO,KAAKP,IAAI;QACfC,OAAOM,KAAKN,KAAK;QACjBC,aAAaK,KAAKL,WAAW;QAC7BC,KAAKI,KAAKJ,GAAG;QACbQ,UAAU;QACVC,SAAQT,GAAQ;YACd,OAAO;gBACLU,UAAU;oBAAC;wBAAEV,KAAKA,IAAIW,IAAI;wBAAEC,MAAMP;oBAAK;iBAAE;YAC3C;QACF;IACF;AACF"}
|
package/dist/types.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* payload-mcp-toolkit configuration.
|
|
3
|
-
*
|
|
4
|
-
* The plugin works with zero options — every field below is an escape hatch
|
|
5
|
-
* for the cases where Payload's own config doesn't carry enough signal.
|
|
1
|
+
/**
|
|
2
|
+
* payload-mcp-toolkit configuration.
|
|
3
|
+
*
|
|
4
|
+
* The plugin works with zero options — every field below is an escape hatch
|
|
5
|
+
* for the cases where Payload's own config doesn't carry enough signal.
|
|
6
6
|
*/ /** Relationship edge in the collection graph */ export { };
|
|
7
7
|
|
|
8
8
|
//# sourceMappingURL=types.js.map
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * payload-mcp-toolkit configuration.\n *\n * The plugin works with zero options — every field below is an escape hatch\n * for the cases where Payload's own config doesn't carry enough signal.\n */\nexport interface ContentToolkitOptions {\n /**\n * Preview URL behavior. The toolkit reads `collection.admin.livePreview.url`\n * (or `collection.admin.preview` as a fallback) when generating preview links\n * for draft documents. Provide this object only to override what Payload\n * already knows.\n */\n preview?: {\n /**\n * Absolute base URL prepended to relative preview paths. Defaults to\n * `incomingConfig.serverURL`, then `process.env.NEXT_PUBLIC_SERVER_URL`,\n * then `process.env.SITE_URL`. If none of those resolve and your preview\n * URL function returns a relative path, no preview URL is appended.\n */\n siteUrl?: string\n\n /**\n * Disable preview URL injection entirely.\n */\n disabled?: boolean\n }\n\n /**\n * Per-collection draft behavior overrides. The default behavior is inferred\n * from each collection's `versions.drafts` setting:\n * - drafts enabled → `'always-draft'` (raw `update` is locked; clients go\n * through `publishDraft` / `patchLayout` / `updateDocument` which preserve\n * draft semantics)\n * - drafts disabled → `'always-publish'`\n *\n * Override per slug only if you need to allow raw publish on a draftable\n * collection.\n */\n draftBehavior?: Record<string, 'always-draft' | 'always-publish'>\n\n /**\n * Override the auth collection used for API key linkage. By default the\n * toolkit scans `incomingConfig.collections` for the first collection with\n * `auth: true`, preferring one named `'users'`.\n */\n userCollection?: string\n\n /**\n * Hide collections or globals from the MCP surface. Useful for internal\n * bookkeeping collections that should not be exposed to AI clients.\n */\n exclude?: {\n collections?: string[]\n globals?: string[]\n }\n\n /**\n * Site-specific domain prompts that teach the AI business vocabulary.\n * Merged with the auto-generated prompts.\n */\n domainPrompts?: DomainPrompt[]\n\n /** Media upload configuration */\n mediaUpload?: {\n /** Maximum file size in bytes (default: 10MB) */\n maxFileSize?: number\n /** Media collection slug (default: 'media') */\n collectionSlug?: string\n }\n}\n\n/** A domain prompt that teaches the AI site-specific vocabulary */\nexport interface DomainPrompt {\n /** Unique name for the prompt */\n name: string\n /** Display title */\n title: string\n /** Description of what this prompt teaches */\n description: string\n /** The prompt content */\n content: string\n}\n\n/** Introspected field metadata */\nexport interface FieldSchema {\n name: string\n type: string\n required?: boolean\n hasMany?: boolean\n relationTo?: string | string[]\n options?: Array<{ label: string; value: string }>\n fields?: FieldSchema[]\n maxRows?: number\n}\n\n/** Introspected collection metadata */\nexport interface CollectionSchema {\n slug: string\n fields: FieldSchema[]\n hasDrafts: boolean\n hasLivePreview: boolean\n relationships: Array<{ fieldName: string; relationTo: string | string[]; hasMany: boolean }>\n searchableFields: string[]\n}\n\n/**\n * One block in the catalog. Flat — no section/leaf distinction. Whether a\n * block can nest other blocks is encoded in the `BlockNestingMap` keyed by\n * the path to its `blocks` field.\n */\nexport interface BlockSchema {\n slug: string\n fields: FieldSchema[]\n}\n\n/**\n * Flat catalog of every block referenced by the schema.\n */\nexport interface BlockCatalog {\n blocks: BlockSchema[]\n}\n\n/**\n * One entry per `blocks`-typed field anywhere in the schema.\n *\n * `path` is `<owner>.<dottedFieldPath>` where owner is the collection or\n * block slug that contains the field. Values list the slugs that field\n * accepts. The AI uses this to compose blocks at any nesting depth without\n * us pre-classifying anything as a \"section\" or \"leaf\".\n */\nexport interface BlockNestingEdge {\n /** Owner of the blocks field — either a collection slug or a block slug */\n owner: string\n /** Whether the owner is a collection or a block */\n ownerType: 'collection' | 'block'\n /** Dotted path to the blocks field within the owner (e.g. `layout`, `hero.content`) */\n fieldPath: string\n /** Block slugs that this field accepts */\n acceptedBlockSlugs: string[]\n /** Optional row cap from the field config */\n maxRows?: number\n}\n\n/** Map of every blocks-field in the schema to the slugs it accepts */\nexport type BlockNestingMap = BlockNestingEdge[]\n\n/** Relationship edge in the collection graph */\nexport interface RelationshipEdge {\n fromCollection: string\n fieldName: string\n toCollection: string | string[]\n hasMany: boolean\n}\n"],"names":[],"mappings":"AAAA;;;;;CAKC,GA8ID,8CAA8C,GAC9C,WAKC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payload-mcp-toolkit",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Schema-aware MCP toolkit for Payload CMS — wraps the official @payloadcms/plugin-mcp with introspected prompts, resources, draft workflow, and AI-friendly tools so non-technical editors can manage content via AI chat.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "jon8800",
|