payload-mcp-toolkit 0.3.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +232 -151
- package/dist/__tests__/api-keys.test.js +292 -0
- package/dist/__tests__/api-keys.test.js.map +1 -0
- package/dist/__tests__/auth-strategy.test.js +681 -0
- package/dist/__tests__/auth-strategy.test.js.map +1 -0
- package/dist/__tests__/conflict-detection.test.js +69 -0
- package/dist/__tests__/conflict-detection.test.js.map +1 -0
- package/dist/__tests__/delete-document.test.js +70 -0
- package/dist/__tests__/delete-document.test.js.map +1 -0
- package/dist/__tests__/endpoint.test.js +143 -0
- package/dist/__tests__/endpoint.test.js.map +1 -0
- package/dist/__tests__/find-document.test.js +178 -0
- package/dist/__tests__/find-document.test.js.map +1 -0
- package/dist/__tests__/find-global.test.js +173 -0
- package/dist/__tests__/find-global.test.js.map +1 -0
- package/dist/__tests__/global-versions.test.js +183 -0
- package/dist/__tests__/global-versions.test.js.map +1 -0
- package/dist/__tests__/hash.test.js +58 -0
- package/dist/__tests__/hash.test.js.map +1 -0
- package/dist/__tests__/index-integration.test.js +191 -0
- package/dist/__tests__/index-integration.test.js.map +1 -0
- package/dist/__tests__/introspection.test.js +201 -1
- package/dist/__tests__/introspection.test.js.map +1 -1
- package/dist/__tests__/patch-global-layout.test.js +474 -0
- package/dist/__tests__/patch-global-layout.test.js.map +1 -0
- package/dist/__tests__/patch-layout.test.js +171 -0
- package/dist/__tests__/patch-layout.test.js.map +1 -0
- package/dist/__tests__/registry.test.js +795 -0
- package/dist/__tests__/registry.test.js.map +1 -0
- package/dist/__tests__/resources.test.js +139 -0
- package/dist/__tests__/resources.test.js.map +1 -0
- package/dist/__tests__/update-global.test.js +157 -0
- package/dist/__tests__/update-global.test.js.map +1 -0
- package/dist/api-keys.d.ts +46 -0
- package/dist/api-keys.js +272 -0
- package/dist/api-keys.js.map +1 -0
- package/dist/auth-strategy.d.ts +85 -0
- package/dist/auth-strategy.js +219 -0
- package/dist/auth-strategy.js.map +1 -0
- package/dist/components/CollectionScopesMatrix.d.ts +8 -0
- package/dist/components/CollectionScopesMatrix.js +32 -0
- package/dist/components/CollectionScopesMatrix.js.map +1 -0
- package/dist/components/GlobalScopesMatrix.d.ts +8 -0
- package/dist/components/GlobalScopesMatrix.js +28 -0
- package/dist/components/GlobalScopesMatrix.js.map +1 -0
- package/dist/components/ScopesTable.d.ts +19 -0
- package/dist/components/ScopesTable.js +285 -0
- package/dist/components/ScopesTable.js.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +4 -0
- package/dist/components/index.js.map +1 -0
- package/dist/conflict-detection.d.ts +13 -0
- package/dist/conflict-detection.js +41 -0
- package/dist/conflict-detection.js.map +1 -0
- package/dist/draft-workflow.d.ts +46 -48
- package/dist/draft-workflow.js +53 -135
- package/dist/draft-workflow.js.map +1 -1
- package/dist/endpoint.d.ts +35 -0
- package/dist/endpoint.js +105 -0
- package/dist/endpoint.js.map +1 -0
- package/dist/hash.d.ts +21 -0
- package/dist/hash.js +36 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.js +167 -69
- package/dist/index.js.map +1 -1
- package/dist/introspection.d.ts +17 -3
- package/dist/introspection.js +95 -36
- package/dist/introspection.js.map +1 -1
- package/dist/prompts.js +5 -5
- package/dist/prompts.js.map +1 -1
- package/dist/registry.d.ts +50 -0
- package/dist/registry.js +169 -0
- package/dist/registry.js.map +1 -0
- package/dist/resources.d.ts +5 -3
- package/dist/resources.js +23 -11
- package/dist/resources.js.map +1 -1
- package/dist/scope/audit-log.d.ts +18 -0
- package/dist/scope/audit-log.js +50 -0
- package/dist/scope/audit-log.js.map +1 -0
- package/dist/scope/policy.d.ts +73 -0
- package/dist/scope/policy.js +218 -0
- package/dist/scope/policy.js.map +1 -0
- package/dist/tools/_helpers.d.ts +28 -1
- package/dist/tools/_helpers.js +83 -0
- package/dist/tools/_helpers.js.map +1 -1
- package/dist/tools/_layout-helpers.d.ts +43 -0
- package/dist/tools/_layout-helpers.js +159 -0
- package/dist/tools/_layout-helpers.js.map +1 -0
- package/dist/tools/create-document.d.ts +5 -5
- package/dist/tools/create-document.js +25 -21
- package/dist/tools/create-document.js.map +1 -1
- package/dist/tools/delete-document.d.ts +25 -0
- package/dist/tools/delete-document.js +49 -0
- package/dist/tools/delete-document.js.map +1 -0
- package/dist/tools/find-document.d.ts +33 -0
- package/dist/tools/find-document.js +97 -0
- package/dist/tools/find-document.js.map +1 -0
- package/dist/tools/find-global.d.ts +26 -0
- package/dist/tools/find-global.js +122 -0
- package/dist/tools/find-global.js.map +1 -0
- package/dist/tools/global-versions.d.ts +39 -0
- package/dist/tools/global-versions.js +132 -0
- package/dist/tools/global-versions.js.map +1 -0
- package/dist/tools/patch-global-layout.d.ts +31 -0
- package/dist/tools/patch-global-layout.js +127 -0
- package/dist/tools/patch-global-layout.js.map +1 -0
- package/dist/tools/patch-layout.d.ts +5 -8
- package/dist/tools/patch-layout.js +18 -100
- package/dist/tools/patch-layout.js.map +1 -1
- package/dist/tools/publish-draft.d.ts +5 -4
- package/dist/tools/publish-draft.js +6 -1
- package/dist/tools/publish-draft.js.map +1 -1
- package/dist/tools/publish-global-draft.d.ts +20 -0
- package/dist/tools/publish-global-draft.js +50 -0
- package/dist/tools/publish-global-draft.js.map +1 -0
- package/dist/tools/resolve-reference.d.ts +5 -4
- package/dist/tools/resolve-reference.js +4 -0
- package/dist/tools/resolve-reference.js.map +1 -1
- package/dist/tools/safe-delete.d.ts +5 -5
- package/dist/tools/safe-delete.js +20 -15
- package/dist/tools/safe-delete.js.map +1 -1
- package/dist/tools/schedule-publish.d.ts +5 -5
- package/dist/tools/schedule-publish.js +23 -19
- package/dist/tools/schedule-publish.js.map +1 -1
- package/dist/tools/search-content.d.ts +5 -9
- package/dist/tools/search-content.js +16 -12
- package/dist/tools/search-content.js.map +1 -1
- package/dist/tools/update-document.d.ts +5 -5
- package/dist/tools/update-document.js +10 -5
- package/dist/tools/update-document.js.map +1 -1
- package/dist/tools/update-global.d.ts +27 -0
- package/dist/tools/update-global.js +72 -0
- package/dist/tools/update-global.js.map +1 -0
- package/dist/tools/upload-media.d.ts +5 -4
- package/dist/tools/upload-media.js +6 -1
- package/dist/tools/upload-media.js.map +1 -1
- package/dist/tools/versions.d.ts +10 -9
- package/dist/tools/versions.js +15 -7
- package/dist/tools/versions.js.map +1 -1
- package/dist/types.d.ts +56 -3
- package/dist/types.js +13 -6
- package/dist/types.js.map +1 -1
- package/package.json +11 -4
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
1
|
+
{"version":3,"sources":["../src/introspection.ts"],"sourcesContent":["import type { Block, CollectionConfig, Field, GlobalConfig } from 'payload'\r\nimport type {\r\n BlockCatalog,\r\n BlockNestingEdge,\r\n BlockNestingMap,\r\n BlockSchema,\r\n CollectionSchema,\r\n FieldSchema,\r\n GlobalSchema,\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 * True if the global has Payload drafts enabled in its versions config.\r\n * Mirrors `hasCollectionDrafts` — globals use the same `versions.drafts`\r\n * shape as collections.\r\n */\r\nexport function hasGlobalDrafts(global: GlobalConfig): boolean {\r\n const versions = global.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 global config into structured metadata.\r\n */\r\nexport function introspectGlobal(global: GlobalConfig): GlobalSchema {\r\n const fields = extractFields(global.fields)\r\n const hasLivePreview = !!(\r\n global.admin &&\r\n typeof global.admin === 'object' &&\r\n 'livePreview' in global.admin &&\r\n global.admin.livePreview\r\n )\r\n\r\n return {\r\n slug: global.slug,\r\n fields,\r\n hasDrafts: hasGlobalDrafts(global),\r\n hasLivePreview,\r\n }\r\n}\r\n\r\n/**\r\n * Introspect all globals into a map keyed by slug.\r\n */\r\nexport function introspectGlobals(globals: GlobalConfig[]): Map<string, GlobalSchema> {\r\n const map = new Map<string, GlobalSchema>()\r\n for (const global of globals) {\r\n map.set(global.slug, introspectGlobal(global))\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 globalsOrBlocks: GlobalConfig[] | Block[],\r\n blocks?: Block[],\r\n): BlockNestingMap {\r\n // Back-compat overload: when called with two args, the second is `blocks`\r\n // and there are no globals. The new shape is `(collections, globals, blocks)`.\r\n const globals: GlobalConfig[] = blocks === undefined ? [] : (globalsOrBlocks as GlobalConfig[])\r\n const effectiveBlocks: Block[] =\r\n blocks === undefined ? (globalsOrBlocks as Block[]) : blocks\r\n\r\n const knownSlugs = new Set(effectiveBlocks.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 global of globals) {\r\n edges.push(\r\n ...collectBlocksFieldEdges(global.fields, {\r\n owner: global.slug,\r\n ownerType: 'global',\r\n prefix: '',\r\n knownSlugs,\r\n }),\r\n )\r\n }\r\n\r\n for (const block of effectiveBlocks) {\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 assertBlockNestingMapInvariant(edges)\r\n return edges\r\n}\r\n\r\n/**\r\n * Guard against `(owner, fieldPath)` collisions across different `ownerType`\r\n * values. Payload's own slug registry forbids collection/global slug\r\n * collisions, but a defensive check here keeps the validator lookup in\r\n * `patchLayout` / `patchGlobalLayout` unambiguous and surfaces config\r\n * mistakes loudly instead of silently picking the first match.\r\n */\r\nfunction assertBlockNestingMapInvariant(edges: BlockNestingMap): void {\r\n const seen = new Map<string, BlockNestingEdge['ownerType']>()\r\n for (const edge of edges) {\r\n const key = `${edge.owner}.${edge.fieldPath}`\r\n const prior = seen.get(key)\r\n if (prior && prior !== edge.ownerType) {\r\n throw new Error(\r\n `[payload-mcp-toolkit] Block-nesting map invariant violated: ` +\r\n `\"${key}\" appears as both \"${prior}\" and \"${edge.ownerType}\". ` +\r\n `A collection and a global cannot share a slug; rename one.`,\r\n )\r\n }\r\n seen.set(key, edge.ownerType)\r\n }\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' | 'global'\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","hasGlobalDrafts","global","introspectGlobal","introspectGlobals","globals","introspectBlocks","blocks","catalog","block","buildBlockNestingMap","globalsOrBlocks","undefined","effectiveBlocks","knownSlugs","Set","b","edges","push","collectBlocksFieldEdges","owner","ownerType","prefix","assertBlockNestingMapInvariant","seen","edge","key","fieldPath","prior","get","Error","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","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 {\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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"}
|
|
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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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
import { ZodObject, type ZodTypeAny } from 'zod';
|
|
3
|
+
import type { InitializeServerForRequest } from './endpoint';
|
|
4
|
+
import { type McpTextResponse } from './tools/_helpers';
|
|
5
|
+
import { buildScopeChecker, type ResourceKind, type RoutingTables, type ScopeChecker, type ScopeDecision, type ToolRouting } from './scope/policy';
|
|
6
|
+
export interface ToolFactoryOutput {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
/**
|
|
10
|
+
* Either a raw Zod shape (`{ key: ZodType }`) or a `z.object({...})`
|
|
11
|
+
* instance. The registry normalises both before registering with the SDK.
|
|
12
|
+
*/
|
|
13
|
+
parameters: Record<string, ZodTypeAny> | ZodObject<Record<string, ZodTypeAny>>;
|
|
14
|
+
handler: (args: Record<string, unknown>, req: PayloadRequest, extra: unknown) => Promise<McpTextResponse> | McpTextResponse;
|
|
15
|
+
routing: ToolRouting;
|
|
16
|
+
}
|
|
17
|
+
export interface PromptFactoryOutput {
|
|
18
|
+
name: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
argsSchema?: Record<string, ZodTypeAny>;
|
|
22
|
+
handler: (args: unknown, req: PayloadRequest, extra: unknown) => unknown;
|
|
23
|
+
}
|
|
24
|
+
export interface ResourceFactoryOutput {
|
|
25
|
+
name: string;
|
|
26
|
+
uri: string;
|
|
27
|
+
title?: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
mimeType?: string;
|
|
30
|
+
handler: (args: unknown, req: PayloadRequest, extra: unknown) => unknown;
|
|
31
|
+
}
|
|
32
|
+
export { buildScopeChecker, type ResourceKind, type RoutingTables, type ScopeChecker, type ScopeDecision, type ToolRouting, };
|
|
33
|
+
export interface CreateInitializeServerOptions {
|
|
34
|
+
tools: ToolFactoryOutput[];
|
|
35
|
+
prompts?: PromptFactoryOutput[];
|
|
36
|
+
resources?: ResourceFactoryOutput[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Builds the per-request initializer. mcp-handler invokes this once per
|
|
40
|
+
* `tools/call` / `tools/list` JSON-RPC request, passing a fresh McpServer.
|
|
41
|
+
*
|
|
42
|
+
* Each tool handler is wrapped to:
|
|
43
|
+
* 1. Read the API-key context populated by the bearer auth strategy.
|
|
44
|
+
* 2. Reject the call (as an `isError: true` result, not a JSON-RPC error)
|
|
45
|
+
* when scopes deny it — per the MCP spec, tool-execution failures
|
|
46
|
+
* surface in the result envelope so the LLM can self-correct.
|
|
47
|
+
* 3. Stamp `req.context.source = 'mcp'` for downstream hooks.
|
|
48
|
+
* 4. Emit a structured audit log entry on every success / failure.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createInitializeServer(options: CreateInitializeServerOptions): InitializeServerForRequest;
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { ZodObject } from 'zod';
|
|
2
|
+
import { getApiKeyContext } from './auth-strategy';
|
|
3
|
+
import { stampMcpContext } from './tools/_helpers';
|
|
4
|
+
import { assertScopeAllows, buildRoutingTables, buildScopeChecker } from './scope/policy';
|
|
5
|
+
import { extractDataKeys, getRequestId, makeSafeLog, summariseArgs } from './scope/audit-log';
|
|
6
|
+
/**
|
|
7
|
+
* Returns the raw `{ name: ZodType }` shape for either a raw shape or a
|
|
8
|
+
* `z.object({...})` instance. The MCP SDK's `registerTool` expects the raw
|
|
9
|
+
* shape under `inputSchema`; passing a ZodObject silently registers an
|
|
10
|
+
* empty schema and breaks args validation.
|
|
11
|
+
*/ function toZodShape(parameters) {
|
|
12
|
+
if (parameters instanceof ZodObject) {
|
|
13
|
+
return parameters.shape;
|
|
14
|
+
}
|
|
15
|
+
return parameters;
|
|
16
|
+
}
|
|
17
|
+
// Re-export scope-routing primitives so existing callers keep working.
|
|
18
|
+
export { buildScopeChecker, };
|
|
19
|
+
function scopeRejectionResult(reason) {
|
|
20
|
+
return {
|
|
21
|
+
content: [
|
|
22
|
+
{
|
|
23
|
+
type: 'text',
|
|
24
|
+
text: `Scope rejection: ${reason}`
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
isError: true
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Builds the per-request initializer. mcp-handler invokes this once per
|
|
32
|
+
* `tools/call` / `tools/list` JSON-RPC request, passing a fresh McpServer.
|
|
33
|
+
*
|
|
34
|
+
* Each tool handler is wrapped to:
|
|
35
|
+
* 1. Read the API-key context populated by the bearer auth strategy.
|
|
36
|
+
* 2. Reject the call (as an `isError: true` result, not a JSON-RPC error)
|
|
37
|
+
* when scopes deny it — per the MCP spec, tool-execution failures
|
|
38
|
+
* surface in the result envelope so the LLM can self-correct.
|
|
39
|
+
* 3. Stamp `req.context.source = 'mcp'` for downstream hooks.
|
|
40
|
+
* 4. Emit a structured audit log entry on every success / failure.
|
|
41
|
+
*/ export function createInitializeServer(options) {
|
|
42
|
+
const { tools, prompts = [], resources = [] } = options;
|
|
43
|
+
const tables = buildRoutingTables(tools);
|
|
44
|
+
return (req)=>(server)=>{
|
|
45
|
+
const logger = req.payload?.logger;
|
|
46
|
+
const requestId = getRequestId(req);
|
|
47
|
+
const safeLog = makeSafeLog(logger);
|
|
48
|
+
for (const tool of tools){
|
|
49
|
+
const resourceKind = tables.toolKind.get(tool.name) ?? null;
|
|
50
|
+
const wrapped = async (args, extra)=>{
|
|
51
|
+
const start = Date.now();
|
|
52
|
+
const keyCtx = getApiKeyContext(req);
|
|
53
|
+
const targetSlug = typeof args.collection === 'string' ? args.collection : typeof args.slug === 'string' ? args.slug : undefined;
|
|
54
|
+
const targetKind = resourceKind ?? undefined;
|
|
55
|
+
const dataKeys = extractDataKeys(args);
|
|
56
|
+
const decision = assertScopeAllows(keyCtx?.scopes ?? null, tool.name, targetSlug, tables);
|
|
57
|
+
if (!decision.allowed) {
|
|
58
|
+
safeLog('warn', {
|
|
59
|
+
event: 'mcp.tool_call',
|
|
60
|
+
keyId: keyCtx?.keyId,
|
|
61
|
+
keyPrefix: keyCtx?.keyPrefix,
|
|
62
|
+
tool: tool.name,
|
|
63
|
+
targetSlug,
|
|
64
|
+
targetKind,
|
|
65
|
+
dataKeys,
|
|
66
|
+
success: false,
|
|
67
|
+
isError: true,
|
|
68
|
+
durationMs: Date.now() - start,
|
|
69
|
+
requestId,
|
|
70
|
+
errorClass: 'ScopeRejection'
|
|
71
|
+
}, `[payload-mcp-toolkit] Scope-rejected tool call: ${tool.name}`);
|
|
72
|
+
return scopeRejectionResult(decision.reason ?? 'denied');
|
|
73
|
+
}
|
|
74
|
+
stampMcpContext(req);
|
|
75
|
+
try {
|
|
76
|
+
const result = await tool.handler(args, req, extra);
|
|
77
|
+
safeLog('info', {
|
|
78
|
+
event: 'mcp.tool_call',
|
|
79
|
+
keyId: keyCtx?.keyId,
|
|
80
|
+
keyPrefix: keyCtx?.keyPrefix,
|
|
81
|
+
tool: tool.name,
|
|
82
|
+
targetSlug,
|
|
83
|
+
targetKind,
|
|
84
|
+
dataKeys,
|
|
85
|
+
success: true,
|
|
86
|
+
isError: false,
|
|
87
|
+
durationMs: Date.now() - start,
|
|
88
|
+
requestId
|
|
89
|
+
}, `[payload-mcp-toolkit] Tool call: ${tool.name}`);
|
|
90
|
+
return result;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
const errorClass = err instanceof Error ? err.name : 'UnknownError';
|
|
93
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
94
|
+
safeLog('error', {
|
|
95
|
+
event: 'mcp.tool_call',
|
|
96
|
+
err,
|
|
97
|
+
keyId: keyCtx?.keyId,
|
|
98
|
+
keyPrefix: keyCtx?.keyPrefix,
|
|
99
|
+
tool: tool.name,
|
|
100
|
+
targetSlug,
|
|
101
|
+
targetKind,
|
|
102
|
+
dataKeys,
|
|
103
|
+
argsSummary: summariseArgs(args),
|
|
104
|
+
success: false,
|
|
105
|
+
isError: true,
|
|
106
|
+
durationMs: Date.now() - start,
|
|
107
|
+
requestId,
|
|
108
|
+
errorClass
|
|
109
|
+
}, `[payload-mcp-toolkit] Tool call failed: ${tool.name}`);
|
|
110
|
+
return {
|
|
111
|
+
content: [
|
|
112
|
+
{
|
|
113
|
+
type: 'text',
|
|
114
|
+
text: `Error: ${message}`
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
isError: true
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
server.registerTool(tool.name, {
|
|
122
|
+
description: tool.description,
|
|
123
|
+
inputSchema: toZodShape(tool.parameters)
|
|
124
|
+
}, wrapped);
|
|
125
|
+
}
|
|
126
|
+
for (const prompt of prompts){
|
|
127
|
+
const wrapped = async (args, extra)=>{
|
|
128
|
+
try {
|
|
129
|
+
return await prompt.handler(args, req, extra);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
logger?.error?.({
|
|
132
|
+
event: 'mcp.prompt',
|
|
133
|
+
err,
|
|
134
|
+
prompt: prompt.name,
|
|
135
|
+
requestId
|
|
136
|
+
}, `[payload-mcp-toolkit] Prompt failed: ${prompt.name}`);
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
server.registerPrompt(prompt.name, {
|
|
141
|
+
title: prompt.title,
|
|
142
|
+
description: prompt.description,
|
|
143
|
+
argsSchema: prompt.argsSchema
|
|
144
|
+
}, wrapped);
|
|
145
|
+
}
|
|
146
|
+
for (const resource of resources){
|
|
147
|
+
const wrapped = async (args, extra)=>{
|
|
148
|
+
try {
|
|
149
|
+
return await resource.handler(args, req, extra);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
logger?.error?.({
|
|
152
|
+
event: 'mcp.resource',
|
|
153
|
+
err,
|
|
154
|
+
resource: resource.name,
|
|
155
|
+
requestId
|
|
156
|
+
}, `[payload-mcp-toolkit] Resource read failed: ${resource.name}`);
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
server.registerResource(resource.name, resource.uri, {
|
|
161
|
+
title: resource.title,
|
|
162
|
+
description: resource.description,
|
|
163
|
+
mimeType: resource.mimeType
|
|
164
|
+
}, wrapped);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/registry.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\r\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\r\nimport { ZodObject, type ZodTypeAny } from 'zod'\r\nimport { getApiKeyContext } from './auth-strategy'\r\nimport type { InitializeServerForRequest } from './endpoint'\r\nimport { stampMcpContext, type McpTextResponse } from './tools/_helpers'\r\nimport {\r\n assertScopeAllows,\r\n buildRoutingTables,\r\n buildScopeChecker,\r\n type ResourceKind,\r\n type RoutingTables,\r\n type ScopeChecker,\r\n type ScopeDecision,\r\n type ToolRouting,\r\n} from './scope/policy'\r\nimport { extractDataKeys, getRequestId, makeSafeLog, summariseArgs } from './scope/audit-log'\r\n\r\n// ─── Tool / Prompt / Resource shapes ──────────────────────────────────\r\n\r\nexport interface ToolFactoryOutput {\r\n name: string\r\n description: string\r\n /**\r\n * Either a raw Zod shape (`{ key: ZodType }`) or a `z.object({...})`\r\n * instance. The registry normalises both before registering with the SDK.\r\n */\r\n parameters: Record<string, ZodTypeAny> | ZodObject<Record<string, ZodTypeAny>>\r\n handler: (\r\n args: Record<string, unknown>,\r\n req: PayloadRequest,\r\n extra: unknown,\r\n ) => Promise<McpTextResponse> | McpTextResponse\r\n routing: ToolRouting\r\n}\r\n\r\n/**\r\n * Returns the raw `{ name: ZodType }` shape for either a raw shape or a\r\n * `z.object({...})` instance. The MCP SDK's `registerTool` expects the raw\r\n * shape under `inputSchema`; passing a ZodObject silently registers an\r\n * empty schema and breaks args validation.\r\n */\r\nfunction toZodShape(\r\n parameters: Record<string, ZodTypeAny> | ZodObject<Record<string, ZodTypeAny>>,\r\n): Record<string, ZodTypeAny> {\r\n if (parameters instanceof ZodObject) {\r\n return parameters.shape as Record<string, ZodTypeAny>\r\n }\r\n return parameters\r\n}\r\n\r\nexport interface PromptFactoryOutput {\r\n name: string\r\n title?: string\r\n description?: string\r\n argsSchema?: Record<string, ZodTypeAny>\r\n handler: (args: unknown, req: PayloadRequest, extra: unknown) => unknown\r\n}\r\n\r\nexport interface ResourceFactoryOutput {\r\n name: string\r\n uri: string\r\n title?: string\r\n description?: string\r\n mimeType?: string\r\n handler: (args: unknown, req: PayloadRequest, extra: unknown) => unknown\r\n}\r\n\r\n// Re-export scope-routing primitives so existing callers keep working.\r\nexport {\r\n buildScopeChecker,\r\n type ResourceKind,\r\n type RoutingTables,\r\n type ScopeChecker,\r\n type ScopeDecision,\r\n type ToolRouting,\r\n}\r\n\r\n// ─── Initializer factory ─────────────────────────────────────────────\r\n\r\nexport interface CreateInitializeServerOptions {\r\n tools: ToolFactoryOutput[]\r\n prompts?: PromptFactoryOutput[]\r\n resources?: ResourceFactoryOutput[]\r\n}\r\n\r\ninterface ScopeRejectionResult {\r\n content: Array<{ type: 'text'; text: string }>\r\n isError: true\r\n}\r\n\r\nfunction scopeRejectionResult(reason: string): ScopeRejectionResult {\r\n return {\r\n content: [{ type: 'text', text: `Scope rejection: ${reason}` }],\r\n isError: true,\r\n }\r\n}\r\n\r\n/**\r\n * Builds the per-request initializer. mcp-handler invokes this once per\r\n * `tools/call` / `tools/list` JSON-RPC request, passing a fresh McpServer.\r\n *\r\n * Each tool handler is wrapped to:\r\n * 1. Read the API-key context populated by the bearer auth strategy.\r\n * 2. Reject the call (as an `isError: true` result, not a JSON-RPC error)\r\n * when scopes deny it — per the MCP spec, tool-execution failures\r\n * surface in the result envelope so the LLM can self-correct.\r\n * 3. Stamp `req.context.source = 'mcp'` for downstream hooks.\r\n * 4. Emit a structured audit log entry on every success / failure.\r\n */\r\nexport function createInitializeServer(\r\n options: CreateInitializeServerOptions,\r\n): InitializeServerForRequest {\r\n const { tools, prompts = [], resources = [] } = options\r\n const tables: RoutingTables = buildRoutingTables(tools)\r\n\r\n return (req: PayloadRequest) => (server: McpServer) => {\r\n const logger = req.payload?.logger\r\n const requestId = getRequestId(req)\r\n const safeLog = makeSafeLog(logger)\r\n\r\n for (const tool of tools) {\r\n const resourceKind = tables.toolKind.get(tool.name) ?? null\r\n const wrapped = async (\r\n args: Record<string, unknown>,\r\n extra: unknown,\r\n ): Promise<unknown> => {\r\n const start = Date.now()\r\n const keyCtx = getApiKeyContext(req)\r\n const targetSlug =\r\n typeof args.collection === 'string'\r\n ? args.collection\r\n : typeof args.slug === 'string'\r\n ? args.slug\r\n : undefined\r\n const targetKind = resourceKind ?? undefined\r\n const dataKeys = extractDataKeys(args)\r\n\r\n const decision = assertScopeAllows(\r\n keyCtx?.scopes ?? null,\r\n tool.name,\r\n targetSlug,\r\n tables,\r\n )\r\n\r\n if (!decision.allowed) {\r\n safeLog(\r\n 'warn',\r\n {\r\n event: 'mcp.tool_call',\r\n keyId: keyCtx?.keyId,\r\n keyPrefix: keyCtx?.keyPrefix,\r\n tool: tool.name,\r\n targetSlug,\r\n targetKind,\r\n dataKeys,\r\n success: false,\r\n isError: true,\r\n durationMs: Date.now() - start,\r\n requestId,\r\n errorClass: 'ScopeRejection',\r\n },\r\n `[payload-mcp-toolkit] Scope-rejected tool call: ${tool.name}`,\r\n )\r\n return scopeRejectionResult(decision.reason ?? 'denied')\r\n }\r\n\r\n stampMcpContext(req)\r\n\r\n try {\r\n const result = await tool.handler(args, req, extra)\r\n safeLog(\r\n 'info',\r\n {\r\n event: 'mcp.tool_call',\r\n keyId: keyCtx?.keyId,\r\n keyPrefix: keyCtx?.keyPrefix,\r\n tool: tool.name,\r\n targetSlug,\r\n targetKind,\r\n dataKeys,\r\n success: true,\r\n isError: false,\r\n durationMs: Date.now() - start,\r\n requestId,\r\n },\r\n `[payload-mcp-toolkit] Tool call: ${tool.name}`,\r\n )\r\n return result\r\n } catch (err) {\r\n const errorClass = err instanceof Error ? err.name : 'UnknownError'\r\n const message = err instanceof Error ? err.message : String(err)\r\n safeLog(\r\n 'error',\r\n {\r\n event: 'mcp.tool_call',\r\n err,\r\n keyId: keyCtx?.keyId,\r\n keyPrefix: keyCtx?.keyPrefix,\r\n tool: tool.name,\r\n targetSlug,\r\n targetKind,\r\n dataKeys,\r\n argsSummary: summariseArgs(args),\r\n success: false,\r\n isError: true,\r\n durationMs: Date.now() - start,\r\n requestId,\r\n errorClass,\r\n },\r\n `[payload-mcp-toolkit] Tool call failed: ${tool.name}`,\r\n )\r\n return {\r\n content: [{ type: 'text', text: `Error: ${message}` }],\r\n isError: true,\r\n }\r\n }\r\n }\r\n\r\n server.registerTool(\r\n tool.name,\r\n {\r\n description: tool.description,\r\n inputSchema: toZodShape(tool.parameters),\r\n },\r\n wrapped as never,\r\n )\r\n }\r\n\r\n for (const prompt of prompts) {\r\n const wrapped = async (args: unknown, extra: unknown) => {\r\n try {\r\n return await prompt.handler(args, req, extra)\r\n } catch (err) {\r\n logger?.error?.(\r\n { event: 'mcp.prompt', err, prompt: prompt.name, requestId },\r\n `[payload-mcp-toolkit] Prompt failed: ${prompt.name}`,\r\n )\r\n throw err\r\n }\r\n }\r\n server.registerPrompt(\r\n prompt.name,\r\n {\r\n title: prompt.title,\r\n description: prompt.description,\r\n argsSchema: prompt.argsSchema as never,\r\n },\r\n wrapped as never,\r\n )\r\n }\r\n\r\n for (const resource of resources) {\r\n const wrapped = async (args: unknown, extra: unknown) => {\r\n try {\r\n return await resource.handler(args, req, extra)\r\n } catch (err) {\r\n logger?.error?.(\r\n { event: 'mcp.resource', err, resource: resource.name, requestId },\r\n `[payload-mcp-toolkit] Resource read failed: ${resource.name}`,\r\n )\r\n throw err\r\n }\r\n }\r\n server.registerResource(\r\n resource.name,\r\n resource.uri as never,\r\n {\r\n title: resource.title,\r\n description: resource.description,\r\n mimeType: resource.mimeType,\r\n },\r\n wrapped as never,\r\n )\r\n }\r\n }\r\n}\r\n"],"names":["ZodObject","getApiKeyContext","stampMcpContext","assertScopeAllows","buildRoutingTables","buildScopeChecker","extractDataKeys","getRequestId","makeSafeLog","summariseArgs","toZodShape","parameters","shape","scopeRejectionResult","reason","content","type","text","isError","createInitializeServer","options","tools","prompts","resources","tables","req","server","logger","payload","requestId","safeLog","tool","resourceKind","toolKind","get","name","wrapped","args","extra","start","Date","now","keyCtx","targetSlug","collection","slug","undefined","targetKind","dataKeys","decision","scopes","allowed","event","keyId","keyPrefix","success","durationMs","errorClass","result","handler","err","Error","message","String","argsSummary","registerTool","description","inputSchema","prompt","error","registerPrompt","title","argsSchema","resource","registerResource","uri","mimeType"],"mappings":"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"}
|
package/dist/resources.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import type { BlockCatalog, BlockNestingMap, CollectionSchema, RelationshipEdge } from './types';
|
|
1
|
+
import type { BlockCatalog, BlockNestingMap, CollectionSchema, GlobalSchema, RelationshipEdge } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Generate MCP resources that expose the introspected schema as static JSON.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Resources:
|
|
6
6
|
* - blocks://catalog — flat list of every block and its fields
|
|
7
7
|
* - blocks://nesting — per-blocks-field map of which slugs each field accepts
|
|
8
|
+
* (includes global-owned edges when globals contain blocks fields)
|
|
8
9
|
* - collections://schema — collection field metadata
|
|
9
10
|
* - collections://relationships — collection relationship graph
|
|
11
|
+
* - globals://schema — global field metadata (when any global is registered)
|
|
10
12
|
*/
|
|
11
|
-
export declare function generateResources(schemas: Map<string, CollectionSchema>, catalog: BlockCatalog, nesting: BlockNestingMap, relationships: RelationshipEdge[]): {
|
|
13
|
+
export declare function generateResources(schemas: Map<string, CollectionSchema>, catalog: BlockCatalog, nesting: BlockNestingMap, relationships: RelationshipEdge[], globalSchemas?: Map<string, GlobalSchema>): {
|
|
12
14
|
name: string;
|
|
13
15
|
title: string;
|
|
14
16
|
description: string;
|
package/dist/resources.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate MCP resources that expose the introspected schema as static JSON.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
-
* -
|
|
8
|
-
* - collections://
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Generate MCP resources that expose the introspected schema as static JSON.
|
|
3
|
+
*
|
|
4
|
+
* 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
|
+
* (includes global-owned edges when globals contain blocks fields)
|
|
8
|
+
* - collections://schema — collection field metadata
|
|
9
|
+
* - collections://relationships — collection relationship graph
|
|
10
|
+
* - globals://schema — global field metadata (when any global is registered)
|
|
11
|
+
*/ export function generateResources(schemas, catalog, nesting, relationships, globalSchemas = new Map()) {
|
|
12
|
+
const resources = [
|
|
11
13
|
buildJsonResource({
|
|
12
14
|
name: 'blockCatalog',
|
|
13
15
|
title: 'Block Catalog',
|
|
@@ -18,7 +20,7 @@
|
|
|
18
20
|
buildJsonResource({
|
|
19
21
|
name: 'blockNesting',
|
|
20
22
|
title: 'Block Nesting Map',
|
|
21
|
-
description: '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.',
|
|
23
|
+
description: 'For every blocks-typed field in the schema (in collections, globals, and inside other blocks), lists the block slugs that field accepts. Use this to compose nested layouts at any depth.',
|
|
22
24
|
uri: 'blocks://nesting',
|
|
23
25
|
payload: nesting
|
|
24
26
|
}),
|
|
@@ -37,6 +39,16 @@
|
|
|
37
39
|
payload: relationships
|
|
38
40
|
})
|
|
39
41
|
];
|
|
42
|
+
if (globalSchemas.size > 0) {
|
|
43
|
+
resources.push(buildJsonResource({
|
|
44
|
+
name: 'globalSchema',
|
|
45
|
+
title: 'Global Schema',
|
|
46
|
+
description: 'JSON schema of all globals — fields, select options, draft and live-preview flags. Globals are singletons addressed by slug; pair with the findGlobal / updateGlobal tools.',
|
|
47
|
+
uri: 'globals://schema',
|
|
48
|
+
payload: Object.fromEntries(globalSchemas)
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
return resources;
|
|
40
52
|
}
|
|
41
53
|
function buildJsonResource(args) {
|
|
42
54
|
const json = JSON.stringify(args.payload, null, 2);
|
package/dist/resources.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 *
|
|
1
|
+
{"version":3,"sources":["../src/resources.ts"],"sourcesContent":["import type {\r\n BlockCatalog,\r\n BlockNestingMap,\r\n CollectionSchema,\r\n GlobalSchema,\r\n RelationshipEdge,\r\n} from './types'\r\n\r\n/**\r\n * Generate MCP resources that expose the introspected schema as static JSON.\r\n *\r\n * Resources:\r\n * - blocks://catalog — flat list of every block and its fields\r\n * - blocks://nesting — per-blocks-field map of which slugs each field accepts\r\n * (includes global-owned edges when globals contain blocks fields)\r\n * - collections://schema — collection field metadata\r\n * - collections://relationships — collection relationship graph\r\n * - globals://schema — global field metadata (when any global is registered)\r\n */\r\nexport function generateResources(\r\n schemas: Map<string, CollectionSchema>,\r\n catalog: BlockCatalog,\r\n nesting: BlockNestingMap,\r\n relationships: RelationshipEdge[],\r\n globalSchemas: Map<string, GlobalSchema> = new Map(),\r\n) {\r\n const resources = [\r\n buildJsonResource({\r\n name: 'blockCatalog',\r\n title: 'Block Catalog',\r\n description:\r\n 'Flat list of every block type and its fields. Pair with the blockNesting resource to know where each block can be placed.',\r\n uri: 'blocks://catalog',\r\n payload: catalog,\r\n }),\r\n buildJsonResource({\r\n name: 'blockNesting',\r\n title: 'Block Nesting Map',\r\n description:\r\n 'For every blocks-typed field in the schema (in collections, globals, and inside other blocks), lists the block slugs that field accepts. Use this to compose nested layouts at any depth.',\r\n uri: 'blocks://nesting',\r\n payload: nesting,\r\n }),\r\n buildJsonResource({\r\n name: 'collectionSchema',\r\n title: 'Collection Schema',\r\n description:\r\n 'JSON schema of all collections — fields, select options, and relationship targets.',\r\n uri: 'collections://schema',\r\n payload: Object.fromEntries(schemas),\r\n }),\r\n buildJsonResource({\r\n name: 'relationshipGraph',\r\n title: 'Relationship Graph',\r\n description:\r\n 'JSON representation of the collection relationship graph — which collections link to which.',\r\n uri: 'collections://relationships',\r\n payload: relationships,\r\n }),\r\n ]\r\n\r\n if (globalSchemas.size > 0) {\r\n resources.push(\r\n buildJsonResource({\r\n name: 'globalSchema',\r\n title: 'Global Schema',\r\n description:\r\n 'JSON schema of all globals — fields, select options, draft and live-preview flags. Globals are singletons addressed by slug; pair with the findGlobal / updateGlobal tools.',\r\n uri: 'globals://schema',\r\n payload: Object.fromEntries(globalSchemas),\r\n }),\r\n )\r\n }\r\n\r\n return resources\r\n}\r\n\r\nfunction buildJsonResource(args: {\r\n name: string\r\n title: string\r\n description: string\r\n uri: string\r\n payload: unknown\r\n}) {\r\n const json = JSON.stringify(args.payload, null, 2)\r\n return {\r\n name: args.name,\r\n title: args.title,\r\n description: args.description,\r\n uri: args.uri,\r\n mimeType: 'application/json',\r\n handler(uri: URL) {\r\n return {\r\n contents: [{ uri: uri.href, text: json }],\r\n }\r\n },\r\n }\r\n}\r\n\r\n"],"names":["generateResources","schemas","catalog","nesting","relationships","globalSchemas","Map","resources","buildJsonResource","name","title","description","uri","payload","Object","fromEntries","size","push","args","json","JSON","stringify","mimeType","handler","contents","href","text"],"mappings":"AAQA;;;;;;;;;;CAUC,GACD,OAAO,SAASA,kBACdC,OAAsC,EACtCC,OAAqB,EACrBC,OAAwB,EACxBC,aAAiC,EACjCC,gBAA2C,IAAIC,KAAK;IAEpD,MAAMC,YAAY;QAChBC,kBAAkB;YAChBC,MAAM;YACNC,OAAO;YACPC,aACE;YACFC,KAAK;YACLC,SAASX;QACX;QACAM,kBAAkB;YAChBC,MAAM;YACNC,OAAO;YACPC,aACE;YACFC,KAAK;YACLC,SAASV;QACX;QACAK,kBAAkB;YAChBC,MAAM;YACNC,OAAO;YACPC,aACE;YACFC,KAAK;YACLC,SAASC,OAAOC,WAAW,CAACd;QAC9B;QACAO,kBAAkB;YAChBC,MAAM;YACNC,OAAO;YACPC,aACE;YACFC,KAAK;YACLC,SAAST;QACX;KACD;IAED,IAAIC,cAAcW,IAAI,GAAG,GAAG;QAC1BT,UAAUU,IAAI,CACZT,kBAAkB;YAChBC,MAAM;YACNC,OAAO;YACPC,aACE;YACFC,KAAK;YACLC,SAASC,OAAOC,WAAW,CAACV;QAC9B;IAEJ;IAEA,OAAOE;AACT;AAEA,SAASC,kBAAkBU,IAM1B;IACC,MAAMC,OAAOC,KAAKC,SAAS,CAACH,KAAKL,OAAO,EAAE,MAAM;IAChD,OAAO;QACLJ,MAAMS,KAAKT,IAAI;QACfC,OAAOQ,KAAKR,KAAK;QACjBC,aAAaO,KAAKP,WAAW;QAC7BC,KAAKM,KAAKN,GAAG;QACbU,UAAU;QACVC,SAAQX,GAAQ;YACd,OAAO;gBACLY,UAAU;oBAAC;wBAAEZ,KAAKA,IAAIa,IAAI;wBAAEC,MAAMP;oBAAK;iBAAE;YAC3C;QACF;IACF;AACF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the top-level keys of a JSON-string `data` arg, sanitized.
|
|
4
|
+
* Per the Codex post-planning finding: logging key names (not values) lets us
|
|
5
|
+
* later analyze whether the prose-only input shape is causing AI mistakes.
|
|
6
|
+
*/
|
|
7
|
+
export declare function extractDataKeys(args: Record<string, unknown>): string[] | undefined;
|
|
8
|
+
export declare function summariseArgs(args: Record<string, unknown>): Record<string, unknown>;
|
|
9
|
+
export declare function getRequestId(req: PayloadRequest): string | undefined;
|
|
10
|
+
type LoggerLike = Partial<Record<'info' | 'warn' | 'error', (...args: unknown[]) => unknown>>;
|
|
11
|
+
/**
|
|
12
|
+
* Returns a logger-invoker that swallows transport failures. Audit-log
|
|
13
|
+
* writes must never break the tool dispatch path — a throwing logger
|
|
14
|
+
* transport (closed stream during HMR, a custom pino dest) would otherwise
|
|
15
|
+
* flip a success to isError or mask the real tool error.
|
|
16
|
+
*/
|
|
17
|
+
export declare function makeSafeLog(logger: LoggerLike | undefined): (level: "info" | "warn" | "error", payload: Record<string, unknown>, message: string) => void;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// ─── Audit logging helpers ───────────────────────────────────────────
|
|
2
|
+
const MAX_LOGGED_STRING = 200;
|
|
3
|
+
/**
|
|
4
|
+
* Returns the top-level keys of a JSON-string `data` arg, sanitized.
|
|
5
|
+
* Per the Codex post-planning finding: logging key names (not values) lets us
|
|
6
|
+
* later analyze whether the prose-only input shape is causing AI mistakes.
|
|
7
|
+
*/ export function extractDataKeys(args) {
|
|
8
|
+
const data = args.data;
|
|
9
|
+
if (typeof data !== 'string') return undefined;
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(data);
|
|
12
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
13
|
+
return Object.keys(parsed);
|
|
14
|
+
}
|
|
15
|
+
} catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
export function summariseArgs(args) {
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const [k, v] of Object.entries(args)){
|
|
23
|
+
if (typeof v === 'string' && v.length > MAX_LOGGED_STRING) {
|
|
24
|
+
out[k] = `<truncated:${v.length}>`;
|
|
25
|
+
} else {
|
|
26
|
+
out[k] = v;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
export function getRequestId(req) {
|
|
32
|
+
const headers = req.headers;
|
|
33
|
+
return headers?.get?.('x-request-id') ?? undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Returns a logger-invoker that swallows transport failures. Audit-log
|
|
37
|
+
* writes must never break the tool dispatch path — a throwing logger
|
|
38
|
+
* transport (closed stream during HMR, a custom pino dest) would otherwise
|
|
39
|
+
* flip a success to isError or mask the real tool error.
|
|
40
|
+
*/ export function makeSafeLog(logger) {
|
|
41
|
+
return (level, payload, message)=>{
|
|
42
|
+
try {
|
|
43
|
+
logger?.[level]?.(payload, message);
|
|
44
|
+
} catch {
|
|
45
|
+
// Logger transport failure must not break dispatch.
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=audit-log.js.map
|