@abinnovision/payloadcms-mcpx 1.0.1-beta.1 → 1.1.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -4
- package/dist/api-keys/capability-matrix.d.mts +32 -0
- package/dist/api-keys/capability-matrix.mjs +128 -0
- package/dist/api-keys/fields.mjs +28 -26
- package/dist/client/capability-matrix.d.mts +26 -0
- package/dist/client/capability-matrix.mjs +349 -0
- package/dist/client/index.d.mts +2 -1
- package/dist/client/index.mjs +2 -1
- package/dist/client/setup-guide.d.mts +2 -3
- package/dist/options.d.mts +2 -0
- package/dist/plugin.d.mts +2 -3
- package/dist/request.d.mts +2 -3
- package/dist/result.d.mts +3 -4
- package/dist/types.d.mts +18 -19
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -330,6 +330,25 @@ to anyone who may read the key document (own keys only, by default). Each key:
|
|
|
330
330
|
is configured `write: "live"`, and it counts only alongside `write`, since
|
|
331
331
|
publishing is an extension of writing.
|
|
332
332
|
|
|
333
|
+
### The capabilities matrix
|
|
334
|
+
|
|
335
|
+
The checkboxes render as one table per namespace: a row per collection, global
|
|
336
|
+
or tool, a column per operation, and a bulk toggle in each column header that
|
|
337
|
+
grants or clears the whole column at once. Clicking a row's name does the same
|
|
338
|
+
for that entity.
|
|
339
|
+
|
|
340
|
+
A cell the plugin config does not expose shows a dash instead of an empty box,
|
|
341
|
+
so a `write: false` collection reads as a refusal by config rather than a
|
|
342
|
+
capability someone forgot to tick. A column no row exposes is left out
|
|
343
|
+
altogether. Ticking `publish` ticks `write` with it, and clearing `write` clears
|
|
344
|
+
`publish`, because a publish without a write resolves to nothing.
|
|
345
|
+
|
|
346
|
+
Each operation is explained on its column header rather than beside every
|
|
347
|
+
checkbox, so the same sentence is never repeated down the table.
|
|
348
|
+
|
|
349
|
+
The matrix renders an admin component, so it has to be in the import map; see
|
|
350
|
+
[Admin components](#admin-components).
|
|
351
|
+
|
|
333
352
|
Keys authenticate only the MCP endpoint. They are deliberately not a Payload
|
|
334
353
|
auth strategy, so a key can never authenticate the REST or GraphQL API. The
|
|
335
354
|
reverse also holds: an admin session or JWT is ignored by the MCP endpoint.
|
|
@@ -345,15 +364,22 @@ behind a copy button. The tab only exists once the key does, so the create form
|
|
|
345
364
|
stays free of it. Turn it off with `apiKeys.setupGuide: false`, which also drops
|
|
346
365
|
the tabs and restores the flat form.
|
|
347
366
|
|
|
348
|
-
The
|
|
367
|
+
The URL comes from `serverURL` when the config sets one and from the browser's
|
|
368
|
+
origin otherwise.
|
|
369
|
+
|
|
370
|
+
### Admin components
|
|
371
|
+
|
|
372
|
+
The key form renders two components of its own, the capabilities matrix and the
|
|
373
|
+
setup guide, so both have to be in the import map:
|
|
349
374
|
|
|
350
375
|
```bash
|
|
351
376
|
payload generate:importmap
|
|
352
377
|
```
|
|
353
378
|
|
|
354
|
-
Without
|
|
355
|
-
|
|
356
|
-
the
|
|
379
|
+
Without those entries Payload logs a missing-component error and renders nothing
|
|
380
|
+
in their place. For the setup guide that costs a tab; for the matrix it costs
|
|
381
|
+
the whole capabilities editor, leaving no way to grant a key anything from the
|
|
382
|
+
admin. The rest of the plugin is unaffected either way.
|
|
357
383
|
|
|
358
384
|
## Drafts and publishing
|
|
359
385
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import "../options.mjs";
|
|
2
|
+
//#region src/api-keys/capability-matrix.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* One entity's row. The three booleans say what the plugin config exposes, not
|
|
5
|
+
* what the key has been granted: a `false` renders as a dash rather than an
|
|
6
|
+
* empty box, so absence reads as a refusal by config rather than a gap.
|
|
7
|
+
*/
|
|
8
|
+
export interface CapabilityRow {
|
|
9
|
+
fieldName: string;
|
|
10
|
+
/** The slug, which is what MCP clients send and what refusals name. */
|
|
11
|
+
label: string;
|
|
12
|
+
read: boolean;
|
|
13
|
+
write: boolean;
|
|
14
|
+
publish: boolean;
|
|
15
|
+
/** Said only where a row departs from what its column header promises. */
|
|
16
|
+
hint?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface CapabilityTool {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Everything the admin component needs to draw the matrix, derived from the
|
|
24
|
+
* plugin options at config time. Crosses the server/client boundary as a client
|
|
25
|
+
* prop, so every leaf has to stay JSON-serializable.
|
|
26
|
+
*/
|
|
27
|
+
export interface CapabilityMatrix {
|
|
28
|
+
collections: CapabilityRow[];
|
|
29
|
+
globals: CapabilityRow[];
|
|
30
|
+
tools: CapabilityTool[];
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { CAPABILITIES_FIELD, canCreate, canPublish, canWrite } from "../capabilities.mjs";
|
|
2
|
+
//#region src/api-keys/capability-matrix.ts
|
|
3
|
+
/**
|
|
4
|
+
* The group's `admin.description`, which the matrix renders in Payload's own
|
|
5
|
+
* description slot under the heading. It is the one place the two markings in
|
|
6
|
+
* the table are explained, so both belong in it.
|
|
7
|
+
*/ const CAPABILITIES_DESCRIPTION = "What this key may do. An unticked box is a refusal, and a dash means the plugin config does not expose that operation at all.";
|
|
8
|
+
/**
|
|
9
|
+
* The three operations a collection or global can expose, described once. The
|
|
10
|
+
* generated checkboxes and the matrix column headers both read these, so the
|
|
11
|
+
* wording a key's owner sees cannot drift from what the field config carries.
|
|
12
|
+
*/ const CAPABILITY_OPERATIONS = [
|
|
13
|
+
{
|
|
14
|
+
id: "read",
|
|
15
|
+
label: "Read",
|
|
16
|
+
description: "Describe, find and read."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "write",
|
|
20
|
+
label: "Write",
|
|
21
|
+
description: "Create, patch and validate drafts."
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "publish",
|
|
25
|
+
label: "Publish",
|
|
26
|
+
description: "Promote the current draft to what the public sees."
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
const UPLOAD_HINT = "Files are uploaded in the admin panel.";
|
|
30
|
+
const toRow = (entity) => ({
|
|
31
|
+
fieldName: entity.fieldName,
|
|
32
|
+
label: entity.slug,
|
|
33
|
+
read: entity.read,
|
|
34
|
+
write: canWrite(entity),
|
|
35
|
+
publish: canPublish(entity),
|
|
36
|
+
...canWrite(entity) && !canCreate(entity) ? { hint: UPLOAD_HINT } : {}
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* The single description of what the config exposes. `createCapabilityFields`
|
|
40
|
+
* generates its checkboxes from this, and the admin component draws from it, so
|
|
41
|
+
* a cell can never appear without a field behind it.
|
|
42
|
+
*/ const createCapabilityMatrix = (options) => ({
|
|
43
|
+
collections: options.collections.map(toRow),
|
|
44
|
+
globals: options.globals.map(toRow),
|
|
45
|
+
tools: options.tools.map((tool) => ({
|
|
46
|
+
name: tool.name,
|
|
47
|
+
description: typeof tool.description === "string" ? tool.description : tool.name
|
|
48
|
+
}))
|
|
49
|
+
});
|
|
50
|
+
const cellPath = (basePath, namespace, fieldName, operation) => `${basePath}.${namespace}.${fieldName}.${operation}`;
|
|
51
|
+
const toolPath = (basePath, name) => `${basePath}.tools.${name}`;
|
|
52
|
+
/** Every path the matrix can address, in a stable order. */ const capabilityPaths = (matrix, basePath = CAPABILITIES_FIELD) => [...["collections", "globals"].flatMap((namespace) => matrix[namespace].flatMap((row) => CAPABILITY_OPERATIONS.filter((operation) => row[operation.id]).map((operation) => cellPath(basePath, namespace, row.fieldName, operation.id)))), ...matrix.tools.map((tool) => toolPath(basePath, tool.name))];
|
|
53
|
+
const stateOf = (paths, values) => {
|
|
54
|
+
if (paths.length === 0) return "off";
|
|
55
|
+
const granted = paths.filter((path) => values[path] === true).length;
|
|
56
|
+
if (granted === 0) return "off";
|
|
57
|
+
return granted === paths.length ? "on" : "mixed";
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Whether a column's bulk toggle reads as on, off or indeterminate. Non-exposed
|
|
61
|
+
* cells are ignored: a column of two granted cells and one dash is fully on.
|
|
62
|
+
*/ const columnState = (matrix, basePath, namespace, operation, values) => stateOf(matrix[namespace].filter((row) => row[operation]).map((row) => cellPath(basePath, namespace, row.fieldName, operation)), values);
|
|
63
|
+
/**
|
|
64
|
+
* Whether an entity's row toggle reads as on, off or indeterminate, over the
|
|
65
|
+
* operations the config exposes for that row.
|
|
66
|
+
*/ const rowState = (basePath, namespace, row, values) => stateOf(CAPABILITY_OPERATIONS.filter((operation) => row[operation.id]).map((operation) => cellPath(basePath, namespace, row.fieldName, operation.id)), values);
|
|
67
|
+
const toolsState = (matrix, basePath, values) => stateOf(matrix.tools.map((tool) => toolPath(basePath, tool.name)), values);
|
|
68
|
+
/**
|
|
69
|
+
* Publishing is an extension of writing, and `publishFlag` in `capabilities.ts`
|
|
70
|
+
* discards a publish without a write. Ticking publish therefore ticks write,
|
|
71
|
+
* and clearing write clears publish, so the form cannot save a combination the
|
|
72
|
+
* server ignores.
|
|
73
|
+
*/ const reconcile = (next, touched) => {
|
|
74
|
+
if (touched.has("publish") && next.publish) return {
|
|
75
|
+
...next,
|
|
76
|
+
write: true
|
|
77
|
+
};
|
|
78
|
+
if (touched.has("write") && !next.write) return {
|
|
79
|
+
...next,
|
|
80
|
+
publish: false
|
|
81
|
+
};
|
|
82
|
+
return next;
|
|
83
|
+
};
|
|
84
|
+
const rowActions = (basePath, namespace, row, values, changes) => {
|
|
85
|
+
const current = {
|
|
86
|
+
read: values[cellPath(basePath, namespace, row.fieldName, "read")] === true,
|
|
87
|
+
write: values[cellPath(basePath, namespace, row.fieldName, "write")] === true,
|
|
88
|
+
publish: values[cellPath(basePath, namespace, row.fieldName, "publish")] === true
|
|
89
|
+
};
|
|
90
|
+
const touched = /* @__PURE__ */ new Set();
|
|
91
|
+
const next = { ...current };
|
|
92
|
+
for (const operation of CAPABILITY_OPERATIONS) {
|
|
93
|
+
const change = changes[operation.id];
|
|
94
|
+
if (change !== void 0 && row[operation.id]) {
|
|
95
|
+
next[operation.id] = change;
|
|
96
|
+
touched.add(operation.id);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const reconciled = reconcile(next, touched);
|
|
100
|
+
return CAPABILITY_OPERATIONS.filter((operation) => row[operation.id] && reconciled[operation.id] !== current[operation.id]).map((operation) => ({
|
|
101
|
+
type: "UPDATE",
|
|
102
|
+
path: cellPath(basePath, namespace, row.fieldName, operation.id),
|
|
103
|
+
value: reconciled[operation.id]
|
|
104
|
+
}));
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* The form-state updates one click implies, as literal actions. Every toggle in
|
|
108
|
+
* the matrix — cell, row, column or tool — comes through here, so the publish
|
|
109
|
+
* rule holds for bulk grants as much as for a single box.
|
|
110
|
+
*
|
|
111
|
+
* Only cells whose value actually changes get an action, which keeps a bulk
|
|
112
|
+
* toggle from marking the form modified when it changes nothing.
|
|
113
|
+
*/ const buildToggleActions = (matrix, basePath, values, intent) => {
|
|
114
|
+
if (intent.kind === "tool" || intent.kind === "tools") return matrix.tools.filter((tool) => intent.kind === "tools" || tool.name === intent.name).filter((tool) => values[toolPath(basePath, tool.name)] === true !== intent.value).map((tool) => ({
|
|
115
|
+
type: "UPDATE",
|
|
116
|
+
path: toolPath(basePath, tool.name),
|
|
117
|
+
value: intent.value
|
|
118
|
+
}));
|
|
119
|
+
const rows = matrix[intent.namespace].filter((row) => intent.kind === "column" || row.fieldName === intent.fieldName);
|
|
120
|
+
const changes = intent.kind === "row" ? {
|
|
121
|
+
read: intent.value,
|
|
122
|
+
write: intent.value,
|
|
123
|
+
publish: intent.value
|
|
124
|
+
} : { [intent.operation]: intent.value };
|
|
125
|
+
return rows.flatMap((row) => rowActions(basePath, intent.namespace, row, values, changes));
|
|
126
|
+
};
|
|
127
|
+
//#endregion
|
|
128
|
+
export { CAPABILITIES_DESCRIPTION, CAPABILITY_OPERATIONS, buildToggleActions, capabilityPaths, cellPath, columnState, createCapabilityMatrix, rowState, toolPath, toolsState };
|
package/dist/api-keys/fields.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { CAPABILITIES_FIELD
|
|
1
|
+
import { CAPABILITIES_FIELD } from "../capabilities.mjs";
|
|
2
|
+
import { CAPABILITIES_DESCRIPTION, CAPABILITY_OPERATIONS, createCapabilityMatrix } from "./capability-matrix.mjs";
|
|
2
3
|
//#region src/api-keys/fields.ts
|
|
3
4
|
const encryptKey = ({ req, value }) => typeof value === "string" ? req.payload.encrypt(value) : value;
|
|
4
5
|
const decryptKey = ({ req, value }) => {
|
|
@@ -89,10 +90,11 @@ const checkbox = (name, description) => ({
|
|
|
89
90
|
}]
|
|
90
91
|
}];
|
|
91
92
|
};
|
|
92
|
-
const PUBLISH_DESCRIPTION = "Publish the current draft. Changes what the public sees.";
|
|
93
93
|
/**
|
|
94
94
|
* One checkbox per exposed operation, grouped per collection, per global and
|
|
95
|
-
* per custom tool
|
|
95
|
+
* per custom tool, generated from the same {@link createCapabilityMatrix}
|
|
96
|
+
* descriptor the admin component draws, so a cell can never appear without a
|
|
97
|
+
* field behind it. Only operations the plugin config exposes get a checkbox, so
|
|
96
98
|
* a key can never enable more than the config allows. Everything defaults to
|
|
97
99
|
* off, which is why a key issued before a capability existed stays closed to it.
|
|
98
100
|
*
|
|
@@ -100,31 +102,21 @@ const PUBLISH_DESCRIPTION = "Publish the current draft. Changes what the public
|
|
|
100
102
|
* `write: "live"`: there is no draft to promote there, the write itself is the
|
|
101
103
|
* live change, and a second checkbox would only make `write` a dead setting.
|
|
102
104
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
105
|
+
* The group renders as a matrix rather than as nested boxes; see
|
|
106
|
+
* `src/client/capability-matrix.tsx`. The descriptions below therefore reach
|
|
107
|
+
* the screen on the matrix column headers, and survive here for a consumer who
|
|
108
|
+
* replaces the component through `apiKeys.overrideCollection`.
|
|
106
109
|
*/ const createCapabilityFields = (options) => {
|
|
107
|
-
const
|
|
108
|
-
|
|
110
|
+
const matrix = createCapabilityMatrix(options);
|
|
111
|
+
const entityGroups = (rows) => rows.map((row) => ({
|
|
112
|
+
name: row.fieldName,
|
|
109
113
|
type: "group",
|
|
110
|
-
label:
|
|
111
|
-
fields: [
|
|
112
|
-
...collection.read ? [checkbox("read", "Describe, find and read documents.")] : [],
|
|
113
|
-
...canWrite(collection) ? [checkbox("write", canCreate(collection) ? "Create, patch and validate drafts." : "Patch and validate drafts. The file itself is uploaded in the admin panel.")] : [],
|
|
114
|
-
...canPublish(collection) ? [checkbox("publish", PUBLISH_DESCRIPTION)] : []
|
|
115
|
-
]
|
|
114
|
+
label: row.label,
|
|
115
|
+
fields: CAPABILITY_OPERATIONS.filter((operation) => row[operation.id]).map((operation) => checkbox(operation.id, operation.description))
|
|
116
116
|
}));
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
label: global.slug,
|
|
121
|
-
fields: [
|
|
122
|
-
...global.read ? [checkbox("read", "Describe and read this global.")] : [],
|
|
123
|
-
...canWrite(global) ? [checkbox("write", "Patch and validate this global's draft.")] : [],
|
|
124
|
-
...canPublish(global) ? [checkbox("publish", PUBLISH_DESCRIPTION)] : []
|
|
125
|
-
]
|
|
126
|
-
}));
|
|
127
|
-
const toolCheckboxes = options.tools.map((tool) => checkbox(tool.name, typeof tool.description === "string" ? tool.description : tool.name));
|
|
117
|
+
const collectionGroups = entityGroups(matrix.collections);
|
|
118
|
+
const globalGroups = entityGroups(matrix.globals);
|
|
119
|
+
const toolCheckboxes = matrix.tools.map((tool) => checkbox(tool.name, tool.description));
|
|
128
120
|
const groups = [
|
|
129
121
|
...collectionGroups.length > 0 ? [{
|
|
130
122
|
name: "collections",
|
|
@@ -146,7 +138,17 @@ const PUBLISH_DESCRIPTION = "Publish the current draft. Changes what the public
|
|
|
146
138
|
return [{
|
|
147
139
|
name: CAPABILITIES_FIELD,
|
|
148
140
|
type: "group",
|
|
149
|
-
admin: {
|
|
141
|
+
admin: {
|
|
142
|
+
description: CAPABILITIES_DESCRIPTION,
|
|
143
|
+
components: { Field: {
|
|
144
|
+
path: "@abinnovision/payloadcms-mcpx/client",
|
|
145
|
+
exportName: "McpxCapabilityMatrix",
|
|
146
|
+
clientProps: {
|
|
147
|
+
matrix,
|
|
148
|
+
withinTab: options.setupGuide
|
|
149
|
+
}
|
|
150
|
+
} }
|
|
151
|
+
},
|
|
150
152
|
fields: groups
|
|
151
153
|
}];
|
|
152
154
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CapabilityMatrix } from "../api-keys/capability-matrix.mjs";
|
|
2
|
+
import React from "react";
|
|
3
|
+
//#region src/client/capability-matrix.d.ts
|
|
4
|
+
interface McpxCapabilityMatrixProps {
|
|
5
|
+
/** Built from the plugin options at config time. */
|
|
6
|
+
matrix: CapabilityMatrix;
|
|
7
|
+
/** The group's own config, supplied by Payload. Carries the description. */
|
|
8
|
+
field?: {
|
|
9
|
+
admin?: {
|
|
10
|
+
description?: unknown;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/** The `capabilities` group's own path, supplied by Payload. */
|
|
14
|
+
path?: string;
|
|
15
|
+
readOnly?: boolean;
|
|
16
|
+
/** Whether the key form is split into tabs, from the plugin options. */
|
|
17
|
+
withinTab?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* What an API key may do, as one table per namespace: a row per entity, a
|
|
21
|
+
* column per operation, and a bulk toggle in each column header. The nested
|
|
22
|
+
* checkbox fields this replaces still back every cell, so the stored document
|
|
23
|
+
* is unchanged; only the rendering is.
|
|
24
|
+
*/
|
|
25
|
+
export declare const McpxCapabilityMatrix: React.FC<McpxCapabilityMatrixProps>;
|
|
26
|
+
//#endregion
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { CAPABILITIES_FIELD } from "../capabilities.mjs";
|
|
3
|
+
import { CAPABILITY_OPERATIONS, buildToggleActions, capabilityPaths, cellPath, columnState, rowState, toolPath, toolsState } from "../api-keys/capability-matrix.mjs";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { CheckboxInput, FieldDescription, FieldLabel, useForm, useFormFields } from "@payloadcms/ui";
|
|
6
|
+
import { useCallback, useMemo } from "react";
|
|
7
|
+
//#region src/client/capability-matrix.tsx
|
|
8
|
+
const NOT_EXPOSED = "The plugin config does not expose this.";
|
|
9
|
+
const BASE_CLASS = "mcpx-capabilities";
|
|
10
|
+
const SHEET = `
|
|
11
|
+
.${BASE_CLASS} tbody tr:hover { background: var(--theme-elevation-50); }
|
|
12
|
+
.${BASE_CLASS}__toggle:hover { text-decoration: underline; }
|
|
13
|
+
.${BASE_CLASS} section:last-of-type { margin-bottom: 0; }
|
|
14
|
+
`;
|
|
15
|
+
const CELL_PADDING = "calc(var(--base) * 0.6)";
|
|
16
|
+
const EDGE_PADDING = "calc(var(--base) * 0.8)";
|
|
17
|
+
/**
|
|
18
|
+
* Payload's own theme variables, so the matrix follows the admin's light and
|
|
19
|
+
* dark themes without shipping a stylesheet consumers would have to transpile.
|
|
20
|
+
* The header block borrows the admin table's treatment: muted, raised a step
|
|
21
|
+
* off the page and closed with a rule.
|
|
22
|
+
*
|
|
23
|
+
* The only literal left is the small-caps section heading, which has no
|
|
24
|
+
* counterpart in the admin to borrow from.
|
|
25
|
+
*/ const styles = {
|
|
26
|
+
section: { marginBottom: "var(--base)" },
|
|
27
|
+
table: {
|
|
28
|
+
tableLayout: "fixed",
|
|
29
|
+
width: "auto",
|
|
30
|
+
borderCollapse: "separate",
|
|
31
|
+
borderSpacing: 0,
|
|
32
|
+
textAlign: "left"
|
|
33
|
+
},
|
|
34
|
+
head: { background: "var(--theme-elevation-50)" },
|
|
35
|
+
/** The section name, which heads the label column instead of a stray title. */ headTitle: {
|
|
36
|
+
padding: `${CELL_PADDING} ${CELL_PADDING} ${CELL_PADDING} ${EDGE_PADDING}`,
|
|
37
|
+
verticalAlign: "middle",
|
|
38
|
+
fontSize: "0.8em",
|
|
39
|
+
letterSpacing: "0.08em",
|
|
40
|
+
textTransform: "uppercase",
|
|
41
|
+
color: "var(--theme-elevation-400)",
|
|
42
|
+
fontWeight: "normal"
|
|
43
|
+
},
|
|
44
|
+
headOperation: {
|
|
45
|
+
padding: CELL_PADDING,
|
|
46
|
+
verticalAlign: "middle",
|
|
47
|
+
color: "var(--theme-elevation-400)",
|
|
48
|
+
fontWeight: "normal",
|
|
49
|
+
textAlign: "center",
|
|
50
|
+
whiteSpace: "nowrap"
|
|
51
|
+
},
|
|
52
|
+
headLabel: {
|
|
53
|
+
padding: `${CELL_PADDING} ${CELL_PADDING} ${CELL_PADDING} ${EDGE_PADDING}`,
|
|
54
|
+
verticalAlign: "middle",
|
|
55
|
+
color: "var(--theme-elevation-500)",
|
|
56
|
+
fontWeight: "normal"
|
|
57
|
+
},
|
|
58
|
+
/** Closes the header block, across every column rather than the first. */ rule: { borderBottom: "1px solid var(--theme-border-color)" },
|
|
59
|
+
topStart: { borderStartStartRadius: "var(--style-radius-s)" },
|
|
60
|
+
topEnd: { borderStartEndRadius: "var(--style-radius-s)" },
|
|
61
|
+
rowHeader: {
|
|
62
|
+
padding: `${CELL_PADDING} ${CELL_PADDING} ${CELL_PADDING} ${EDGE_PADDING}`,
|
|
63
|
+
verticalAlign: "middle",
|
|
64
|
+
fontWeight: "normal",
|
|
65
|
+
wordBreak: "break-word"
|
|
66
|
+
},
|
|
67
|
+
rowButton: {
|
|
68
|
+
padding: 0,
|
|
69
|
+
border: 0,
|
|
70
|
+
background: "none",
|
|
71
|
+
color: "inherit",
|
|
72
|
+
font: "inherit",
|
|
73
|
+
cursor: "pointer",
|
|
74
|
+
textAlign: "start",
|
|
75
|
+
textDecoration: "none"
|
|
76
|
+
},
|
|
77
|
+
hint: {
|
|
78
|
+
display: "block",
|
|
79
|
+
color: "var(--theme-elevation-400)"
|
|
80
|
+
},
|
|
81
|
+
cell: {
|
|
82
|
+
padding: CELL_PADDING,
|
|
83
|
+
verticalAlign: "middle",
|
|
84
|
+
textAlign: "center"
|
|
85
|
+
},
|
|
86
|
+
lastCell: { paddingInlineEnd: EDGE_PADDING },
|
|
87
|
+
dash: {
|
|
88
|
+
color: "var(--theme-elevation-400)",
|
|
89
|
+
cursor: "help"
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Payload's own checkbox, which carries the admin's styling and the partial
|
|
94
|
+
* state the column and row toggles need. `name` is what it renders as the
|
|
95
|
+
* input's `title`, so it doubles as the control's accessible name.
|
|
96
|
+
*/ const Box = ({ label, onChange, readOnly, state }) => /*#__PURE__*/ jsx(CheckboxInput, {
|
|
97
|
+
checked: state === "on",
|
|
98
|
+
name: label,
|
|
99
|
+
onToggle: onChange,
|
|
100
|
+
partialChecked: state === "mixed",
|
|
101
|
+
readOnly
|
|
102
|
+
});
|
|
103
|
+
/** Both in `--base` multiples, so the columns keep Payload's rhythm. */ const LABEL_WIDTH = "calc(var(--base) * 9)";
|
|
104
|
+
const OPERATION_WIDTH = "calc(var(--base) * 3.5)";
|
|
105
|
+
/** Fixed widths, so every table lines its columns up with the ones above it. */ const Columns = ({ count }) => /*#__PURE__*/ jsxs("colgroup", { children: [/*#__PURE__*/ jsx("col", { style: { width: LABEL_WIDTH } }), Array.from({ length: count }, (_, index) => /*#__PURE__*/ jsx("col", { style: { width: OPERATION_WIDTH } }, index))] });
|
|
106
|
+
/** The rightmost cell carries the table's outer padding, as Payload's does. */ const cellStyle = (isLast) => isLast ? {
|
|
107
|
+
...styles.cell,
|
|
108
|
+
...styles.lastCell
|
|
109
|
+
} : styles.cell;
|
|
110
|
+
const Dash = () => /*#__PURE__*/ jsx("span", {
|
|
111
|
+
"aria-label": NOT_EXPOSED,
|
|
112
|
+
style: styles.dash,
|
|
113
|
+
title: NOT_EXPOSED,
|
|
114
|
+
children: "—"
|
|
115
|
+
});
|
|
116
|
+
/**
|
|
117
|
+
* What an API key may do, as one table per namespace: a row per entity, a
|
|
118
|
+
* column per operation, and a bulk toggle in each column header. The nested
|
|
119
|
+
* checkbox fields this replaces still back every cell, so the stored document
|
|
120
|
+
* is unchanged; only the rendering is.
|
|
121
|
+
*/ const McpxCapabilityMatrix = ({ field, matrix, path = CAPABILITIES_FIELD, readOnly = false, withinTab = false }) => {
|
|
122
|
+
const { dispatchFields, setModified } = useForm();
|
|
123
|
+
const paths = useMemo(() => capabilityPaths(matrix, path), [matrix, path]);
|
|
124
|
+
const encoded = useFormFields(([fields]) => paths.map((cell) => fields[cell]?.value === true ? "1" : "0").join(""));
|
|
125
|
+
const values = useMemo(() => Object.fromEntries(paths.map((cell, index) => [cell, encoded[index] === "1"])), [paths, encoded]);
|
|
126
|
+
const toggle = useCallback((intent) => {
|
|
127
|
+
const actions = buildToggleActions(matrix, path, values, intent);
|
|
128
|
+
if (actions.length === 0) return;
|
|
129
|
+
for (const action of actions) dispatchFields(action);
|
|
130
|
+
setModified(true);
|
|
131
|
+
}, [
|
|
132
|
+
matrix,
|
|
133
|
+
path,
|
|
134
|
+
values,
|
|
135
|
+
dispatchFields,
|
|
136
|
+
setModified
|
|
137
|
+
]);
|
|
138
|
+
const namespaces = [{
|
|
139
|
+
id: "collections",
|
|
140
|
+
title: "Collections"
|
|
141
|
+
}, {
|
|
142
|
+
id: "globals",
|
|
143
|
+
title: "Globals"
|
|
144
|
+
}].filter((namespace) => matrix[namespace.id].length > 0);
|
|
145
|
+
const toolState = toolsState(matrix, path, values);
|
|
146
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
147
|
+
className: [
|
|
148
|
+
"field-type",
|
|
149
|
+
"group-field",
|
|
150
|
+
"group-field--top-level",
|
|
151
|
+
withinTab && "group-field--within-tab",
|
|
152
|
+
BASE_CLASS
|
|
153
|
+
].filter(Boolean).join(" "),
|
|
154
|
+
children: [/*#__PURE__*/ jsx("style", { children: SHEET }), /*#__PURE__*/ jsxs("div", {
|
|
155
|
+
className: "group-field__wrap",
|
|
156
|
+
children: [
|
|
157
|
+
/*#__PURE__*/ jsx("div", {
|
|
158
|
+
className: "group-field__header",
|
|
159
|
+
children: /*#__PURE__*/ jsxs("header", { children: [/*#__PURE__*/ jsx("h3", {
|
|
160
|
+
className: "group-field__title",
|
|
161
|
+
children: /*#__PURE__*/ jsx(FieldLabel, {
|
|
162
|
+
as: "span",
|
|
163
|
+
label: "Capabilities"
|
|
164
|
+
})
|
|
165
|
+
}), typeof field?.admin?.description === "string" ? /*#__PURE__*/ jsx(FieldDescription, {
|
|
166
|
+
description: field.admin.description,
|
|
167
|
+
path
|
|
168
|
+
}) : null] })
|
|
169
|
+
}),
|
|
170
|
+
namespaces.map((namespace) => {
|
|
171
|
+
const rows = matrix[namespace.id];
|
|
172
|
+
const columns = CAPABILITY_OPERATIONS.filter((operation) => rows.some((row) => row[operation.id]));
|
|
173
|
+
return /*#__PURE__*/ jsx("section", {
|
|
174
|
+
style: styles.section,
|
|
175
|
+
children: /*#__PURE__*/ jsxs("table", {
|
|
176
|
+
style: styles.table,
|
|
177
|
+
children: [
|
|
178
|
+
/*#__PURE__*/ jsx(Columns, { count: columns.length }),
|
|
179
|
+
/*#__PURE__*/ jsxs("thead", {
|
|
180
|
+
style: styles.head,
|
|
181
|
+
children: [/*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsx("th", {
|
|
182
|
+
scope: "col",
|
|
183
|
+
style: {
|
|
184
|
+
...styles.headTitle,
|
|
185
|
+
...styles.topStart
|
|
186
|
+
},
|
|
187
|
+
children: namespace.title
|
|
188
|
+
}), columns.map((operation, column) => /*#__PURE__*/ jsx("th", {
|
|
189
|
+
scope: "col",
|
|
190
|
+
style: column === columns.length - 1 ? {
|
|
191
|
+
...styles.headOperation,
|
|
192
|
+
...styles.topEnd
|
|
193
|
+
} : styles.headOperation,
|
|
194
|
+
title: operation.description,
|
|
195
|
+
children: operation.label
|
|
196
|
+
}, operation.id))] }), /*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsx("th", {
|
|
197
|
+
scope: "row",
|
|
198
|
+
style: {
|
|
199
|
+
...styles.headLabel,
|
|
200
|
+
...styles.rule
|
|
201
|
+
},
|
|
202
|
+
children: "All"
|
|
203
|
+
}), columns.map((operation, column) => {
|
|
204
|
+
const state = columnState(matrix, path, namespace.id, operation.id, values);
|
|
205
|
+
return /*#__PURE__*/ jsx("td", {
|
|
206
|
+
style: {
|
|
207
|
+
...cellStyle(column === columns.length - 1),
|
|
208
|
+
...styles.rule
|
|
209
|
+
},
|
|
210
|
+
children: /*#__PURE__*/ jsx(Box, {
|
|
211
|
+
label: `${operation.label} every ${namespace.title.toLowerCase()} entry`,
|
|
212
|
+
onChange: () => {
|
|
213
|
+
toggle({
|
|
214
|
+
kind: "column",
|
|
215
|
+
namespace: namespace.id,
|
|
216
|
+
operation: operation.id,
|
|
217
|
+
value: state !== "on"
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
readOnly,
|
|
221
|
+
state
|
|
222
|
+
})
|
|
223
|
+
}, operation.id);
|
|
224
|
+
})] })]
|
|
225
|
+
}),
|
|
226
|
+
/*#__PURE__*/ jsx("tbody", { children: rows.map((row) => {
|
|
227
|
+
const state = rowState(path, namespace.id, row, values);
|
|
228
|
+
return /*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsxs("th", {
|
|
229
|
+
scope: "row",
|
|
230
|
+
style: styles.rowHeader,
|
|
231
|
+
children: [/*#__PURE__*/ jsx("button", {
|
|
232
|
+
className: `${BASE_CLASS}__toggle`,
|
|
233
|
+
disabled: readOnly,
|
|
234
|
+
onClick: () => {
|
|
235
|
+
toggle({
|
|
236
|
+
kind: "row",
|
|
237
|
+
namespace: namespace.id,
|
|
238
|
+
fieldName: row.fieldName,
|
|
239
|
+
value: state !== "on"
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
style: styles.rowButton,
|
|
243
|
+
title: `Toggle every capability for ${row.label}`,
|
|
244
|
+
type: "button",
|
|
245
|
+
children: row.label
|
|
246
|
+
}), row.hint ? /*#__PURE__*/ jsx("span", {
|
|
247
|
+
style: styles.hint,
|
|
248
|
+
children: row.hint
|
|
249
|
+
}) : null]
|
|
250
|
+
}), columns.map((operation, column) => /*#__PURE__*/ jsx("td", {
|
|
251
|
+
style: cellStyle(column === columns.length - 1),
|
|
252
|
+
children: row[operation.id] ? /*#__PURE__*/ jsx(Box, {
|
|
253
|
+
label: `${operation.label} ${row.label}`,
|
|
254
|
+
onChange: () => {
|
|
255
|
+
toggle({
|
|
256
|
+
kind: "cell",
|
|
257
|
+
namespace: namespace.id,
|
|
258
|
+
fieldName: row.fieldName,
|
|
259
|
+
operation: operation.id,
|
|
260
|
+
value: values[cellPath(path, namespace.id, row.fieldName, operation.id)] !== true
|
|
261
|
+
});
|
|
262
|
+
},
|
|
263
|
+
readOnly,
|
|
264
|
+
state: values[cellPath(path, namespace.id, row.fieldName, operation.id)] === true ? "on" : "off"
|
|
265
|
+
}) : /*#__PURE__*/ jsx(Dash, {})
|
|
266
|
+
}, operation.id))] }, row.fieldName);
|
|
267
|
+
}) })
|
|
268
|
+
]
|
|
269
|
+
})
|
|
270
|
+
}, namespace.id);
|
|
271
|
+
}),
|
|
272
|
+
matrix.tools.length > 0 ? /*#__PURE__*/ jsx("section", {
|
|
273
|
+
style: styles.section,
|
|
274
|
+
children: /*#__PURE__*/ jsxs("table", {
|
|
275
|
+
style: styles.table,
|
|
276
|
+
children: [
|
|
277
|
+
/*#__PURE__*/ jsx(Columns, { count: 1 }),
|
|
278
|
+
/*#__PURE__*/ jsxs("thead", {
|
|
279
|
+
style: styles.head,
|
|
280
|
+
children: [/*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsx("th", {
|
|
281
|
+
scope: "col",
|
|
282
|
+
style: {
|
|
283
|
+
...styles.headTitle,
|
|
284
|
+
...styles.topStart
|
|
285
|
+
},
|
|
286
|
+
children: "Tools"
|
|
287
|
+
}), /*#__PURE__*/ jsx("th", {
|
|
288
|
+
scope: "col",
|
|
289
|
+
style: {
|
|
290
|
+
...styles.headOperation,
|
|
291
|
+
...styles.topEnd
|
|
292
|
+
},
|
|
293
|
+
children: "Enabled"
|
|
294
|
+
})] }), /*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsx("th", {
|
|
295
|
+
scope: "row",
|
|
296
|
+
style: {
|
|
297
|
+
...styles.headLabel,
|
|
298
|
+
...styles.rule
|
|
299
|
+
},
|
|
300
|
+
children: "All"
|
|
301
|
+
}), /*#__PURE__*/ jsx("td", {
|
|
302
|
+
style: {
|
|
303
|
+
...cellStyle(true),
|
|
304
|
+
...styles.rule
|
|
305
|
+
},
|
|
306
|
+
children: /*#__PURE__*/ jsx(Box, {
|
|
307
|
+
label: "Enable every tool",
|
|
308
|
+
onChange: () => {
|
|
309
|
+
toggle({
|
|
310
|
+
kind: "tools",
|
|
311
|
+
value: toolState !== "on"
|
|
312
|
+
});
|
|
313
|
+
},
|
|
314
|
+
readOnly,
|
|
315
|
+
state: toolState
|
|
316
|
+
})
|
|
317
|
+
})] })]
|
|
318
|
+
}),
|
|
319
|
+
/*#__PURE__*/ jsx("tbody", { children: matrix.tools.map((tool) => /*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsxs("th", {
|
|
320
|
+
scope: "row",
|
|
321
|
+
style: styles.rowHeader,
|
|
322
|
+
children: [tool.name, /*#__PURE__*/ jsx("span", {
|
|
323
|
+
style: styles.hint,
|
|
324
|
+
children: tool.description
|
|
325
|
+
})]
|
|
326
|
+
}), /*#__PURE__*/ jsx("td", {
|
|
327
|
+
style: cellStyle(true),
|
|
328
|
+
children: /*#__PURE__*/ jsx(Box, {
|
|
329
|
+
label: `Enable ${tool.name}`,
|
|
330
|
+
onChange: () => {
|
|
331
|
+
toggle({
|
|
332
|
+
kind: "tool",
|
|
333
|
+
name: tool.name,
|
|
334
|
+
value: values[toolPath(path, tool.name)] !== true
|
|
335
|
+
});
|
|
336
|
+
},
|
|
337
|
+
readOnly,
|
|
338
|
+
state: values[toolPath(path, tool.name)] === true ? "on" : "off"
|
|
339
|
+
})
|
|
340
|
+
})] }, tool.name)) })
|
|
341
|
+
]
|
|
342
|
+
})
|
|
343
|
+
}) : null
|
|
344
|
+
]
|
|
345
|
+
})]
|
|
346
|
+
});
|
|
347
|
+
};
|
|
348
|
+
//#endregion
|
|
349
|
+
export { McpxCapabilityMatrix };
|
package/dist/client/index.d.mts
CHANGED
package/dist/client/index.mjs
CHANGED
|
@@ -9,6 +9,5 @@ interface McpxSetupGuideProps {
|
|
|
9
9
|
* until the document is saved, because before that there is no key to hand to
|
|
10
10
|
* a client.
|
|
11
11
|
*/
|
|
12
|
-
declare const McpxSetupGuide: React.FC<McpxSetupGuideProps>;
|
|
13
|
-
//#endregion
|
|
14
|
-
export { McpxSetupGuide };
|
|
12
|
+
export declare const McpxSetupGuide: React.FC<McpxSetupGuideProps>;
|
|
13
|
+
//#endregion
|
package/dist/plugin.d.mts
CHANGED
|
@@ -4,6 +4,5 @@ import { McpxPluginOptions } from "./types.mjs";
|
|
|
4
4
|
* Mounts the MCP endpoint, adds the API key collection and installs the
|
|
5
5
|
* draft guard on every collection and global.
|
|
6
6
|
*/
|
|
7
|
-
declare const mcpxPlugin: (options: McpxPluginOptions) => import("payload").Plugin;
|
|
8
|
-
//#endregion
|
|
9
|
-
export { mcpxPlugin };
|
|
7
|
+
export declare const mcpxPlugin: (options: McpxPluginOptions) => import("payload").Plugin;
|
|
8
|
+
//#endregion
|
package/dist/request.d.mts
CHANGED
|
@@ -5,6 +5,5 @@ import { PayloadRequest } from "payload";
|
|
|
5
5
|
* `req.context.mcpx`, which travels into every local API call made with the
|
|
6
6
|
* same `req`, including those made by custom tools.
|
|
7
7
|
*/
|
|
8
|
-
declare const isMcpxRequest: (req: PayloadRequest) => boolean;
|
|
9
|
-
//#endregion
|
|
10
|
-
export { isMcpxRequest };
|
|
8
|
+
export declare const isMcpxRequest: (req: PayloadRequest) => boolean;
|
|
9
|
+
//#endregion
|
package/dist/result.d.mts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
//#region src/result.d.ts
|
|
3
3
|
/** `value` as JSON text. */
|
|
4
|
-
declare const jsonResult: (value: unknown) => CallToolResult;
|
|
4
|
+
export declare const jsonResult: (value: unknown) => CallToolResult;
|
|
5
5
|
/**
|
|
6
6
|
* `extras` travel alongside the message so the client can act on them:
|
|
7
7
|
* problems, validation errors, the current `updatedAt`.
|
|
8
8
|
*/
|
|
9
|
-
declare const errorResult: (message: string, extras?: Record<string, unknown>) => CallToolResult;
|
|
10
|
-
//#endregion
|
|
11
|
-
export { errorResult, jsonResult };
|
|
9
|
+
export declare const errorResult: (message: string, extras?: Record<string, unknown>) => CallToolResult;
|
|
10
|
+
//#endregion
|
package/dist/types.d.mts
CHANGED
|
@@ -21,9 +21,9 @@ declare module "payload" {
|
|
|
21
21
|
* `publishDocument` is exposed; on one without, where there is no draft to
|
|
22
22
|
* land on, it means the write itself is permitted and lands live.
|
|
23
23
|
*/
|
|
24
|
-
type McpxWriteMode = "draft" | "live" | false;
|
|
24
|
+
export type McpxWriteMode = "draft" | "live" | false;
|
|
25
25
|
/** A key can only enable what the config exposes here. */
|
|
26
|
-
interface McpxCollectionOptions {
|
|
26
|
+
export interface McpxCollectionOptions {
|
|
27
27
|
/** Expose `describeSchema`, `findDocuments`, `getDocument`. Default `true`. */
|
|
28
28
|
read?: boolean;
|
|
29
29
|
/**
|
|
@@ -34,7 +34,7 @@ interface McpxCollectionOptions {
|
|
|
34
34
|
write?: McpxWriteMode;
|
|
35
35
|
}
|
|
36
36
|
/** A singleton, so neither `findDocuments` nor `createDocument` reaches one. */
|
|
37
|
-
interface McpxGlobalOptions {
|
|
37
|
+
export interface McpxGlobalOptions {
|
|
38
38
|
/** Expose `describeSchema` and `getDocument`. Default `true`. */
|
|
39
39
|
read?: boolean;
|
|
40
40
|
/**
|
|
@@ -43,9 +43,9 @@ interface McpxGlobalOptions {
|
|
|
43
43
|
*/
|
|
44
44
|
write?: McpxWriteMode;
|
|
45
45
|
}
|
|
46
|
-
type McpxToolExtra = RequestHandlerExtra<ServerRequest, ServerNotification>;
|
|
46
|
+
export type McpxToolExtra = RequestHandlerExtra<ServerRequest, ServerNotification>;
|
|
47
47
|
/** What the config exposes, before an API key's checkboxes narrow it. */
|
|
48
|
-
interface McpxExposedEntity {
|
|
48
|
+
export interface McpxExposedEntity {
|
|
49
49
|
slug: string;
|
|
50
50
|
read: boolean;
|
|
51
51
|
write: McpxWriteMode;
|
|
@@ -56,7 +56,7 @@ interface McpxExposedEntity {
|
|
|
56
56
|
fieldName: string;
|
|
57
57
|
}
|
|
58
58
|
/** What a tool knows about the current request. */
|
|
59
|
-
interface McpxToolScope {
|
|
59
|
+
export interface McpxToolScope {
|
|
60
60
|
req: PayloadRequest;
|
|
61
61
|
capabilities: McpxResolvedCapabilities;
|
|
62
62
|
readable: string[];
|
|
@@ -82,7 +82,7 @@ interface McpxToolScope {
|
|
|
82
82
|
* `req.context.mcpx` set. `Args` only needs stating when `inputSchema` is built
|
|
83
83
|
* per request, leaving no static shape to infer from.
|
|
84
84
|
*/
|
|
85
|
-
interface McpxTool<Shape extends z.ZodRawShape = z.ZodRawShape, Args = z.infer<z.ZodObject<Shape>>> {
|
|
85
|
+
export interface McpxTool<Shape extends z.ZodRawShape = z.ZodRawShape, Args = z.infer<z.ZodObject<Shape>>> {
|
|
86
86
|
/** camelCase, unique, not one of the builtin tool names. */
|
|
87
87
|
name: string;
|
|
88
88
|
/** Built per request so it can state what this key's writes actually do. */
|
|
@@ -109,23 +109,23 @@ interface McpxTool<Shape extends z.ZodRawShape = z.ZodRawShape, Args = z.infer<z
|
|
|
109
109
|
}): CallToolResult | Promise<CallToolResult>;
|
|
110
110
|
}
|
|
111
111
|
/** Argument type erased, so a registry can hold tools of differing shapes. */
|
|
112
|
-
type McpxAnyTool = McpxTool<z.ZodRawShape, never>;
|
|
112
|
+
export type McpxAnyTool = McpxTool<z.ZodRawShape, never>;
|
|
113
113
|
/** Fixed shape; arguments inferred from it. */
|
|
114
|
-
declare function defineMcpxTool<Shape extends z.ZodRawShape>(tool: McpxTool<Shape> & {
|
|
114
|
+
export declare function defineMcpxTool<Shape extends z.ZodRawShape>(tool: McpxTool<Shape> & {
|
|
115
115
|
inputSchema?: Shape;
|
|
116
116
|
}): McpxTool<Shape>;
|
|
117
117
|
/** Per-request shape returned as an object literal; arguments inferred from it. */
|
|
118
|
-
declare function defineMcpxTool<Shape extends z.ZodRawShape>(tool: McpxTool<Shape> & {
|
|
118
|
+
export declare function defineMcpxTool<Shape extends z.ZodRawShape>(tool: McpxTool<Shape> & {
|
|
119
119
|
inputSchema: (scope: McpxToolScope) => Shape;
|
|
120
120
|
}): McpxAnyTool;
|
|
121
121
|
/**
|
|
122
122
|
* Per-request shape built from helpers that erase to `z.ZodRawShape`, as the
|
|
123
123
|
* builtins do. Nothing to infer from, so state the arguments instead.
|
|
124
124
|
*/
|
|
125
|
-
declare function defineMcpxTool<Args>(tool: McpxTool<z.ZodRawShape, Args> & {
|
|
125
|
+
export declare function defineMcpxTool<Args>(tool: McpxTool<z.ZodRawShape, Args> & {
|
|
126
126
|
inputSchema: (scope: McpxToolScope) => z.ZodRawShape;
|
|
127
127
|
}): McpxAnyTool;
|
|
128
|
-
interface McpxAuthResult {
|
|
128
|
+
export interface McpxAuthResult {
|
|
129
129
|
/** Must carry `collection`. */
|
|
130
130
|
user: TypedUser;
|
|
131
131
|
apiKeyId: number | string;
|
|
@@ -138,7 +138,7 @@ interface McpxAuthResult {
|
|
|
138
138
|
* A type alias rather than an interface: `definePlugin` constrains its options
|
|
139
139
|
* to `Record<string, unknown>`, which interfaces do not satisfy.
|
|
140
140
|
*/
|
|
141
|
-
type McpxPluginOptions = {
|
|
141
|
+
export type McpxPluginOptions = {
|
|
142
142
|
/** Allow-list of collections. `true` is shorthand for `{ read: true }`. */
|
|
143
143
|
collections: Partial<Record<CollectionSlug, McpxCollectionOptions | true>>;
|
|
144
144
|
/** Allow-list of globals. `true` is shorthand for `{ read: true }`. */
|
|
@@ -180,30 +180,29 @@ type McpxPluginOptions = {
|
|
|
180
180
|
};
|
|
181
181
|
};
|
|
182
182
|
/** What a key may do with one entity. Globals reuse this shape. */
|
|
183
|
-
interface McpxCollectionCapabilities {
|
|
183
|
+
export interface McpxCollectionCapabilities {
|
|
184
184
|
read: boolean;
|
|
185
185
|
write: boolean;
|
|
186
186
|
/** Only ever true where the config sets `write: "live"` and drafts exist. */
|
|
187
187
|
publish: boolean;
|
|
188
188
|
}
|
|
189
189
|
/** In force for one request: plugin config AND key checkboxes. */
|
|
190
|
-
interface McpxResolvedCapabilities {
|
|
190
|
+
export interface McpxResolvedCapabilities {
|
|
191
191
|
collections: Record<string, McpxCollectionCapabilities>;
|
|
192
192
|
globals: Record<string, McpxCollectionCapabilities>;
|
|
193
193
|
tools: Record<string, boolean>;
|
|
194
194
|
}
|
|
195
195
|
/** Stamped on `req.context.mcpx`; see {@link isMcpxRequest}. */
|
|
196
|
-
interface McpxRequestContext {
|
|
196
|
+
export interface McpxRequestContext {
|
|
197
197
|
apiKeyId: number | string;
|
|
198
198
|
capabilities: McpxResolvedCapabilities;
|
|
199
199
|
}
|
|
200
200
|
/** One reason a human could not publish the draft as it stands. */
|
|
201
|
-
interface PublishBlocker {
|
|
201
|
+
export interface PublishBlocker {
|
|
202
202
|
/** Resolved field label path, e.g. "Layout > Block 2 (Hero) > Title". */
|
|
203
203
|
field?: string;
|
|
204
204
|
message: string;
|
|
205
205
|
/** JSON Pointer to the offending value, e.g. "/layout/2/title". */
|
|
206
206
|
path: string;
|
|
207
207
|
}
|
|
208
|
-
//#endregion
|
|
209
|
-
export { McpxAnyTool, McpxAuthResult, McpxCollectionCapabilities, McpxCollectionOptions, McpxExposedEntity, McpxGlobalOptions, McpxPluginOptions, McpxRequestContext, McpxResolvedCapabilities, McpxTool, McpxToolExtra, McpxToolScope, McpxWriteMode, PublishBlocker, defineMcpxTool };
|
|
208
|
+
//#endregion
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@abinnovision/payloadcms-mcpx",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.1.0-beta.3",
|
|
5
5
|
"description": "Payload CMS plugin exposing a fixed, schema-aware MCP tool surface with draft-only writes and per-API-key capabilities.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"payload",
|
|
@@ -68,20 +68,20 @@
|
|
|
68
68
|
"@payloadcms/db-sqlite": "3.88.0",
|
|
69
69
|
"@payloadcms/richtext-lexical": "3.88.0",
|
|
70
70
|
"@payloadcms/ui": "3.88.0",
|
|
71
|
-
"@swc/core": "^1.16.
|
|
72
|
-
"@types/node": "^26.
|
|
71
|
+
"@swc/core": "^1.16.2",
|
|
72
|
+
"@types/node": "^26.5.1",
|
|
73
73
|
"@types/react": "^19.2.18",
|
|
74
|
-
"@vitest/coverage-v8": "^
|
|
75
|
-
"eslint": "^10.
|
|
74
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
75
|
+
"eslint": "^10.10.0",
|
|
76
76
|
"payload": "3.88.0",
|
|
77
77
|
"prettier": "^3.9.5",
|
|
78
78
|
"publint": "^0.3.24",
|
|
79
79
|
"react": "^19.2.8",
|
|
80
80
|
"react-dom": "^19.2.8",
|
|
81
|
-
"tsdown": "^0.
|
|
81
|
+
"tsdown": "^0.23.0",
|
|
82
82
|
"typescript": "^6.0.3",
|
|
83
|
-
"unplugin-swc": "^1.
|
|
84
|
-
"vitest": "^
|
|
83
|
+
"unplugin-swc": "^1.6.0",
|
|
84
|
+
"vitest": "^5.0.0"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
87
|
"@payloadcms/ui": ">=3.88.0 <4",
|