@abinnovision/payloadcms-mcpx 2.0.0-beta.9 → 2.0.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/CHANGELOG.md +43 -0
- package/README.md +56 -36
- package/dist/admin/capability-matrix.d.mts +5 -3
- package/dist/admin/capability-matrix.mjs +374 -272
- package/dist/admin/capability-toggles.mjs +79 -42
- package/dist/admin/confirmations-panel.mjs +232 -0
- package/dist/admin/confirmations.d.mts +15 -0
- package/dist/admin/confirmations.mjs +71 -0
- package/dist/admin/endpoint-url.mjs +7 -0
- package/dist/admin/index.d.mts +2 -1
- package/dist/admin/index.mjs +2 -1
- package/dist/admin/setup-guide.d.mts +3 -3
- package/dist/admin/setup-guide.mjs +51 -33
- package/dist/api-keys/capability-matrix.d.mts +7 -9
- package/dist/api-keys/capability-matrix.mjs +15 -37
- package/dist/api-keys/collection.mjs +16 -18
- package/dist/api-keys/confirmation-view.mjs +7 -0
- package/dist/api-keys/fields.mjs +50 -57
- package/dist/auth/resolve.mjs +69 -40
- package/dist/capabilities.d.mts +2 -0
- package/dist/capabilities.mjs +64 -16
- package/dist/define-tool.d.mts +4 -4
- package/dist/define-tool.mjs +8 -1
- package/dist/endpoint/confirmations.mjs +124 -0
- package/dist/endpoint/handler.mjs +25 -27
- package/dist/endpoint/http.mjs +27 -0
- package/dist/endpoint/server.mjs +48 -29
- package/dist/endpoint/upload.mjs +104 -0
- package/dist/grants/confirmation.mjs +75 -0
- package/dist/grants/grant.mjs +137 -0
- package/dist/guards.mjs +8 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/options.mjs +68 -61
- package/dist/plugin.d.mts +3 -2
- package/dist/plugin.mjs +19 -4
- package/dist/request.mjs +5 -1
- package/dist/result.mjs +11 -1
- package/dist/schema/admin-hidden.mjs +4 -4
- package/dist/schema/describe.mjs +12 -18
- package/dist/schema/index.mjs +5 -6
- package/dist/schema/lexical-pointer.mjs +8 -8
- package/dist/schema/lexical.mjs +15 -13
- package/dist/schema/outline.mjs +4 -5
- package/dist/schema/path.mjs +4 -1
- package/dist/schema/pointer.mjs +11 -17
- package/dist/schema/shape.mjs +38 -50
- package/dist/schema/walk.mjs +57 -18
- package/dist/tools/builtin.mjs +7 -1
- package/dist/tools/create-document.mjs +38 -26
- package/dist/tools/delete-document.mjs +154 -0
- package/dist/tools/describe-schema.mjs +7 -5
- package/dist/tools/document.mjs +131 -45
- package/dist/tools/duplicate-document.mjs +50 -0
- package/dist/tools/find-documents.mjs +15 -20
- package/dist/tools/find-versions.mjs +11 -15
- package/dist/tools/get-document.mjs +31 -15
- package/dist/tools/links.mjs +53 -0
- package/dist/tools/list-capabilities.mjs +17 -7
- package/dist/tools/patch-document.mjs +57 -53
- package/dist/tools/publish-document.mjs +17 -29
- package/dist/tools/query-paths.mjs +1 -1
- package/dist/tools/run-confirmed.mjs +114 -0
- package/dist/tools/shared.mjs +53 -46
- package/dist/tools/validate-document.mjs +6 -8
- package/dist/tools/versions.mjs +11 -6
- package/dist/types.d.mts +158 -43
- package/dist/upload/file.mjs +177 -0
- package/dist/version.mjs +4 -2
- package/dist/write/draft-guard.mjs +35 -28
- package/dist/write/patch.mjs +4 -4
- package/dist/write/publish-intent.mjs +23 -11
- package/dist/write/row-ids.mjs +3 -1
- package/dist/write/write-data.mjs +18 -31
- package/package.json +14 -14
- package/dist/builtin-tool-names.mjs +0 -16
package/dist/tools/shared.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { acceptsFiles } from "../upload/file.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
//#region src/tools/shared.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* A value outside the list fails schema validation before a handler runs, so a
|
|
6
6
|
* client only ever sees what its key may touch.
|
|
7
|
-
*/ const
|
|
7
|
+
*/ const stringEnum = (values) => z.enum(values);
|
|
8
8
|
/**
|
|
9
9
|
* Payload's id type follows the adapter, so both forms are handed on as read.
|
|
10
10
|
*/ const idSchema = z.union([z.string(), z.number()]);
|
|
@@ -14,62 +14,50 @@ const slugsOf = (scope, key) => ({
|
|
|
14
14
|
});
|
|
15
15
|
const slugsWhere = (scope, predicate, allowed) => {
|
|
16
16
|
const pick = (entities, slugs) => entities.filter((entity) => slugs.includes(entity.slug) && predicate(entity)).map((entity) => entity.slug);
|
|
17
|
-
return
|
|
17
|
+
return {
|
|
18
|
+
collections: pick(scope.exposure.collections, allowed.collections),
|
|
19
|
+
globals: pick(scope.exposure.globals, allowed.globals)
|
|
20
|
+
};
|
|
18
21
|
};
|
|
19
22
|
/**
|
|
20
|
-
*
|
|
21
|
-
* files
|
|
22
|
-
*/ const patchOnlySlugs = (scope) =>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
23
|
+
* Collections this key may write but not create in: upload collections whose
|
|
24
|
+
* files MCP does not accept. Nothing creates a global either way.
|
|
25
|
+
*/ const patchOnlySlugs = (scope) => {
|
|
26
|
+
const creatable = slugsFor(scope, "create").collections;
|
|
27
|
+
return scope.collections.writable.filter((slug) => !creatable.includes(slug));
|
|
28
|
+
};
|
|
26
29
|
const widen = (branch) => branch;
|
|
27
30
|
/**
|
|
28
31
|
* The one list the shape helpers and {@link resolveEntity} both read.
|
|
29
32
|
*/ const slugsFor = (scope, operation) => {
|
|
30
33
|
switch (operation) {
|
|
31
|
-
case "create": return {
|
|
32
|
-
collections:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
case "create": return slugsWhere(scope, (entity) => entity.write && (!entity.isUpload || acceptsFiles(scope, entity.slug)), {
|
|
35
|
+
collections: scope.collections.writable,
|
|
36
|
+
globals: []
|
|
37
|
+
});
|
|
38
|
+
case "delete": return {
|
|
39
|
+
collections: scope.collections.deletable,
|
|
36
40
|
globals: []
|
|
37
41
|
};
|
|
38
42
|
case "publish": return slugsOf(scope, "publishable");
|
|
39
43
|
case "read": return slugsOf(scope, "readable");
|
|
40
|
-
case "versions": return
|
|
41
|
-
collections: slugsWhere(scope, (entity) => entity.hasVersions, {
|
|
42
|
-
collections: scope.collections.readable,
|
|
43
|
-
globals: []
|
|
44
|
-
}),
|
|
45
|
-
globals: slugsWhere(scope, (entity) => entity.hasVersions, {
|
|
46
|
-
collections: [],
|
|
47
|
-
globals: scope.globals.readable
|
|
48
|
-
})
|
|
49
|
-
};
|
|
44
|
+
case "versions": return slugsWhere(scope, (entity) => entity.hasVersions, slugsOf(scope, "readable"));
|
|
50
45
|
case "write": return slugsOf(scope, "writable");
|
|
51
46
|
}
|
|
52
47
|
};
|
|
53
48
|
/**
|
|
54
|
-
*
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
collections: slugsWhere(scope, (entity) => entity.hasDrafts, {
|
|
60
|
-
collections,
|
|
61
|
-
globals: []
|
|
62
|
-
}),
|
|
63
|
-
globals: slugsWhere(scope, (entity) => entity.hasDrafts, {
|
|
64
|
-
collections: [],
|
|
65
|
-
globals
|
|
66
|
-
})
|
|
67
|
-
};
|
|
49
|
+
* Whether `operation` reaches any collection or global.
|
|
50
|
+
*/ const reaches = (scope, operation) => {
|
|
51
|
+
const { collections, globals } = slugsFor(scope, operation);
|
|
52
|
+
return collections.length + globals.length > 0;
|
|
68
53
|
};
|
|
69
54
|
/**
|
|
70
55
|
* Slugs reachable by `operation` whose writes go live, because they have no
|
|
71
56
|
* draft to write.
|
|
72
|
-
*/ const liveWriteSlugs = (scope, operation) =>
|
|
57
|
+
*/ const liveWriteSlugs = (scope, operation) => {
|
|
58
|
+
const { collections, globals } = slugsWhere(scope, (entity) => entity.liveWrite, slugsFor(scope, operation));
|
|
59
|
+
return [...collections, ...globals];
|
|
60
|
+
};
|
|
73
61
|
/**
|
|
74
62
|
* Stated on the tools that write, since a client calling them must know before
|
|
75
63
|
* the call which writes are public.
|
|
@@ -84,11 +72,11 @@ const widen = (branch) => branch;
|
|
|
84
72
|
* `globalRule` states that rule on `global` for a tool without an `id`.
|
|
85
73
|
*/ const entityShape = (scope, operation, globalRule) => {
|
|
86
74
|
const { collections, globals } = slugsFor(scope, operation);
|
|
87
|
-
if (globals.length === 0) return widen({ collection:
|
|
88
|
-
if (collections.length === 0) return widen({ global:
|
|
89
|
-
const global =
|
|
75
|
+
if (globals.length === 0) return widen({ collection: stringEnum(collections) });
|
|
76
|
+
if (collections.length === 0) return widen({ global: stringEnum(globals) });
|
|
77
|
+
const global = stringEnum(globals).optional();
|
|
90
78
|
return widen({
|
|
91
|
-
collection:
|
|
79
|
+
collection: stringEnum(collections).optional(),
|
|
92
80
|
global: globalRule === void 0 ? global : global.describe(globalRule)
|
|
93
81
|
});
|
|
94
82
|
};
|
|
@@ -106,7 +94,7 @@ const widen = (branch) => branch;
|
|
|
106
94
|
*/ const READ_LOCALE_DESCRIPTION = "Default: the default locale. A value missing in this locale is shown from the default locale.";
|
|
107
95
|
const localeShape = (scope, options) => {
|
|
108
96
|
if (!scope.localization) return widen({});
|
|
109
|
-
const enumerated =
|
|
97
|
+
const enumerated = stringEnum(scope.localization.locales);
|
|
110
98
|
const locale = options.required ? enumerated : enumerated.optional();
|
|
111
99
|
return widen({ locale: options.description === void 0 ? locale : locale.describe(options.description) });
|
|
112
100
|
};
|
|
@@ -115,6 +103,25 @@ const localeShape = (scope, options) => {
|
|
|
115
103
|
* it can write back, and populating a relation costs a query.
|
|
116
104
|
*/ const depthShape = (scope) => ({ depth: z.number().int().min(0).max(scope.limits.maxDepth).optional().describe("Default 0. A relationship into a collection this key cannot read stays an id.") });
|
|
117
105
|
/**
|
|
106
|
+
* For the writes that refuse a document changed since the client read it.
|
|
107
|
+
*/ const expectedUpdatedAtShape = { expectedUpdatedAt: z.string().optional().describe("\"updatedAt\" from your last read. Refused if the document changed since.") };
|
|
108
|
+
/**
|
|
109
|
+
* `limit` is bounded by the configured ceiling, so the client sees it instead
|
|
110
|
+
* of being clamped silently.
|
|
111
|
+
*/ const pageShape = (scope) => ({
|
|
112
|
+
limit: z.number().int().min(1).max(scope.limits.maxLimit).optional().describe("Default 10."),
|
|
113
|
+
page: z.number().int().min(1).optional()
|
|
114
|
+
});
|
|
115
|
+
/**
|
|
116
|
+
* The paging fields of a result.
|
|
117
|
+
*/ const pageFields = (result) => ({
|
|
118
|
+
totalDocs: result.totalDocs,
|
|
119
|
+
page: result.page,
|
|
120
|
+
totalPages: result.totalPages,
|
|
121
|
+
limit: result.limit,
|
|
122
|
+
hasNextPage: result.hasNextPage
|
|
123
|
+
});
|
|
124
|
+
/**
|
|
118
125
|
* The locale to operate on: the explicit argument, else the request's, else
|
|
119
126
|
* the default. `undefined` when localization is off.
|
|
120
127
|
*/ const localeOf = (scope, locale) => {
|
|
@@ -124,4 +131,4 @@ const localeShape = (scope, options) => {
|
|
|
124
131
|
return requested && locales.includes(requested) ? requested : defaultLocale;
|
|
125
132
|
};
|
|
126
133
|
//#endregion
|
|
127
|
-
export { READ_LOCALE_DESCRIPTION, depthShape,
|
|
134
|
+
export { READ_LOCALE_DESCRIPTION, depthShape, entityShape, expectedUpdatedAtShape, idSchema, idShape, liveWriteSentence, liveWriteSlugs, localeOf, localeShape, pageFields, pageShape, patchOnlySlugs, reaches, slugsFor, slugsWhere, stringEnum, widen };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { defineMcpxTool } from "../define-tool.mjs";
|
|
2
2
|
import { jsonResult } from "../result.mjs";
|
|
3
|
-
import { entityShape, idShape, localeOf, localeShape } from "./shared.mjs";
|
|
4
3
|
import { collectPublishBlockers } from "../write/publish-blockers.mjs";
|
|
5
|
-
import {
|
|
4
|
+
import { entityShape, idShape, localeOf, localeShape, reaches } from "./shared.mjs";
|
|
5
|
+
import { collectLocaleBlockers, documentSummary, readDraft, resolveDocument } from "./document.mjs";
|
|
6
6
|
/**
|
|
7
|
-
* Gated on write
|
|
8
|
-
* to a caller who can act on them.
|
|
7
|
+
* Gated on write, which implies read, because publish blockers only mean
|
|
8
|
+
* something to a caller who can act on them.
|
|
9
9
|
*
|
|
10
10
|
* It reads the document twice on purpose: once under the key's own access to
|
|
11
11
|
* refuse a caller who may not see it, then privileged, so the check covers every
|
|
@@ -15,7 +15,7 @@ import { collectLocaleBlockers, identityOf, readDraft, resolveDocument } from ".
|
|
|
15
15
|
name: "validateDocument",
|
|
16
16
|
description: `Lists what still blocks publishing one document or global, without saving. Checks one locale when "locale" is given, otherwise every locale. Returns "publishBlockers", each with a pointer and a message, and a "locale" when every locale was checked. An empty list means it can be published, unless "publishBlockersUnavailable" is true: then the check itself failed. The check runs the field hooks a save runs.`,
|
|
17
17
|
annotations: { openWorldHint: false },
|
|
18
|
-
isEnabled: (scope) => scope
|
|
18
|
+
isEnabled: (scope) => reaches(scope, "write"),
|
|
19
19
|
inputSchema: (scope) => ({
|
|
20
20
|
...entityShape(scope, "write"),
|
|
21
21
|
...idShape(scope, "write"),
|
|
@@ -39,9 +39,7 @@ import { collectLocaleBlockers, identityOf, readDraft, resolveDocument } from ".
|
|
|
39
39
|
entity: target
|
|
40
40
|
});
|
|
41
41
|
return jsonResult({
|
|
42
|
-
...
|
|
43
|
-
status: doc["_status"],
|
|
44
|
-
updatedAt: doc["updatedAt"],
|
|
42
|
+
...documentSummary(target, doc),
|
|
45
43
|
publishBlockers: validation.blockers,
|
|
46
44
|
...validation.unavailable ? { publishBlockersUnavailable: true } : {}
|
|
47
45
|
});
|
package/dist/tools/versions.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { definedProps, isPlainObject } from "../guards.mjs";
|
|
1
2
|
import { stripAdminHidden } from "../schema/admin-hidden.mjs";
|
|
2
3
|
import "../schema/index.mjs";
|
|
3
4
|
import { mcpxReadRequest } from "./read-request.mjs";
|
|
@@ -21,8 +22,10 @@ const NEWEST_FIRST = ["-updatedAt", "-id"];
|
|
|
21
22
|
draft: options.draft,
|
|
22
23
|
overrideAccess: false,
|
|
23
24
|
req: mcpxReadRequest(scope),
|
|
24
|
-
...
|
|
25
|
-
|
|
25
|
+
...definedProps({
|
|
26
|
+
select: options.select,
|
|
27
|
+
locale: read.locale
|
|
28
|
+
})
|
|
26
29
|
};
|
|
27
30
|
const doc = await (read.target.kind === "collection" ? scope.req.payload.findByID({
|
|
28
31
|
...shared,
|
|
@@ -51,7 +54,7 @@ const assertReadable = async (scope, read) => {
|
|
|
51
54
|
* `parent` comes back as a raw id or, populated, as the document itself.
|
|
52
55
|
*/ const isVersionOf = (version, id) => {
|
|
53
56
|
const parent = version["parent"];
|
|
54
|
-
const parentId =
|
|
57
|
+
const parentId = isPlainObject(parent) ? parent["id"] : parent;
|
|
55
58
|
return (typeof parentId === "string" || typeof parentId === "number") && String(parentId) === String(id);
|
|
56
59
|
};
|
|
57
60
|
/**
|
|
@@ -68,7 +71,7 @@ const assertReadable = async (scope, read) => {
|
|
|
68
71
|
disableErrors: true,
|
|
69
72
|
overrideAccess: false,
|
|
70
73
|
req: mcpxReadRequest(scope),
|
|
71
|
-
...
|
|
74
|
+
...definedProps({ locale: read.locale })
|
|
72
75
|
};
|
|
73
76
|
const version = await (read.target.kind === "collection" ? payload.findVersionByID({
|
|
74
77
|
...shared,
|
|
@@ -96,8 +99,10 @@ const assertReadable = async (scope, read) => {
|
|
|
96
99
|
overrideAccess: false,
|
|
97
100
|
req: mcpxReadRequest(scope),
|
|
98
101
|
where,
|
|
99
|
-
...
|
|
100
|
-
|
|
102
|
+
...definedProps({
|
|
103
|
+
page: options.page,
|
|
104
|
+
locale: read.locale
|
|
105
|
+
})
|
|
101
106
|
};
|
|
102
107
|
return await (read.target.kind === "collection" ? scope.req.payload.findVersions({
|
|
103
108
|
...shared,
|
package/dist/types.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DocumentId } from "./entity.mjs";
|
|
2
|
-
import { CollectionConfig, CollectionSlug, GlobalSlug, PayloadRequest, TypedUser } from "payload";
|
|
3
2
|
import { z } from "zod";
|
|
3
|
+
import { CollectionConfig, CollectionSlug, GlobalSlug, PayloadRequest, TypedUser } from "payload";
|
|
4
4
|
import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
5
5
|
import { CallToolResult, ServerNotification, ServerRequest, ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
6
6
|
//#region src/types.d.ts
|
|
@@ -12,17 +12,6 @@ declare module "payload" {
|
|
|
12
12
|
"@abinnovision/payloadcms-mcpx": McpxPluginOptions;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
* How far an exposed entity lets MCP writes reach.
|
|
17
|
-
*
|
|
18
|
-
* - `false`: no write tool touches it.
|
|
19
|
-
* - `"draft"`: writes land as drafts and nothing MCP does changes what the
|
|
20
|
-
* public sees. Requires `versions.drafts`.
|
|
21
|
-
* - `"live"`: MCP may change live content. On an entity with drafts that
|
|
22
|
-
* exposes `publishDocument`. On one without, there is no draft, so the write
|
|
23
|
-
* itself is permitted and lands live.
|
|
24
|
-
*/
|
|
25
|
-
export type McpxWriteMode = "draft" | "live" | false;
|
|
26
15
|
/**
|
|
27
16
|
* What an entity exposes. The config names the entities that are reachable and
|
|
28
17
|
* only takes capabilities away; a key's checkboxes decide per key. `true` is
|
|
@@ -34,9 +23,10 @@ export interface McpxCollectionOptions {
|
|
|
34
23
|
*/
|
|
35
24
|
read?: boolean;
|
|
36
25
|
/**
|
|
37
|
-
* Expose `patchDocument`, `validateDocument` and
|
|
38
|
-
*
|
|
39
|
-
*
|
|
26
|
+
* Expose `patchDocument`, `validateDocument`, `createDocument` and
|
|
27
|
+
* `duplicateDocument`. An upload collection is created in only where MCP
|
|
28
|
+
* accepts its files, and is never duplicated. Without drafts
|
|
29
|
+
* a write changes live content. Needs `read`. Default `true` while `read` is.
|
|
40
30
|
*/
|
|
41
31
|
write?: boolean;
|
|
42
32
|
/**
|
|
@@ -45,20 +35,28 @@ export interface McpxCollectionOptions {
|
|
|
45
35
|
* `false` keeps writes as drafts and is refused where there are none.
|
|
46
36
|
*/
|
|
47
37
|
publish?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Expose `deleteDocument`, which deletes one document once the key's user
|
|
40
|
+
* approved the call in the admin panel. On a collection with `trash` it
|
|
41
|
+
* moves the document to trash. `"unattended"` also lets a key set to it move
|
|
42
|
+
* documents to trash without approval; it needs `trash`. Needs `read`.
|
|
43
|
+
* Default `false`.
|
|
44
|
+
*/
|
|
45
|
+
delete?: boolean | "unattended";
|
|
48
46
|
}
|
|
49
47
|
/**
|
|
50
|
-
* The same options
|
|
51
|
-
* `createDocument` reaches one.
|
|
48
|
+
* The same options without `delete`. A singleton, so neither `findDocuments`,
|
|
49
|
+
* `createDocument` nor `deleteDocument` reaches one.
|
|
52
50
|
*/
|
|
53
|
-
export type McpxGlobalOptions = McpxCollectionOptions
|
|
51
|
+
export type McpxGlobalOptions = Omit<McpxCollectionOptions, "delete">;
|
|
54
52
|
export type McpxToolExtra = RequestHandlerExtra<ServerRequest, ServerNotification>;
|
|
55
53
|
/**
|
|
56
|
-
* What the config exposes, before an API key's checkboxes narrow it.
|
|
54
|
+
* What the config exposes, before an API key's checkboxes narrow it. The
|
|
55
|
+
* operations are the config's side of {@link McpxEntityCapabilities}: `write`
|
|
56
|
+
* covers draft and live writes, and `publish` is only true where drafts exist.
|
|
57
57
|
*/
|
|
58
|
-
export interface McpxExposedEntity {
|
|
58
|
+
export interface McpxExposedEntity extends McpxEntityCapabilities {
|
|
59
59
|
slug: string;
|
|
60
|
-
read: boolean;
|
|
61
|
-
write: McpxWriteMode;
|
|
62
60
|
hasDrafts: boolean;
|
|
63
61
|
/**
|
|
64
62
|
* The entity has Payload `versions`, with or without drafts, and is
|
|
@@ -66,7 +64,11 @@ export interface McpxExposedEntity {
|
|
|
66
64
|
*/
|
|
67
65
|
hasVersions: boolean;
|
|
68
66
|
/**
|
|
69
|
-
*
|
|
67
|
+
* Writes go live immediately, as the entity keeps no drafts.
|
|
68
|
+
*/
|
|
69
|
+
liveWrite: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* An upload document is a file, which only an upload grant can supply.
|
|
70
72
|
*/
|
|
71
73
|
isUpload: boolean;
|
|
72
74
|
/**
|
|
@@ -75,12 +77,13 @@ export interface McpxExposedEntity {
|
|
|
75
77
|
fieldName: string;
|
|
76
78
|
}
|
|
77
79
|
/**
|
|
78
|
-
* The slugs a key may read, write and
|
|
80
|
+
* The slugs a key may read, write, publish and delete.
|
|
79
81
|
*/
|
|
80
82
|
export interface McpxScopeSlugs {
|
|
81
83
|
readable: string[];
|
|
82
84
|
writable: string[];
|
|
83
85
|
publishable: string[];
|
|
86
|
+
deletable: string[];
|
|
84
87
|
}
|
|
85
88
|
/**
|
|
86
89
|
* What a tool knows about the current request.
|
|
@@ -101,17 +104,27 @@ export interface McpxToolScope {
|
|
|
101
104
|
maxLimit: number;
|
|
102
105
|
maxDepth: number;
|
|
103
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* Whether files can be uploaded at all: the KV is database-backed and no
|
|
109
|
+
* custom `auth.resolve` is configured. Confirmations need the same.
|
|
110
|
+
*/
|
|
111
|
+
uploads: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* `null` when `diagnostics` is off.
|
|
114
|
+
*/
|
|
115
|
+
diagnostics: null | {
|
|
116
|
+
name: string;
|
|
117
|
+
version: string;
|
|
118
|
+
};
|
|
104
119
|
exposure: {
|
|
105
120
|
collections: McpxExposedEntity[];
|
|
106
121
|
globals: McpxExposedEntity[];
|
|
107
122
|
};
|
|
108
123
|
}
|
|
109
124
|
/**
|
|
110
|
-
*
|
|
111
|
-
* `req.context.mcpx` set. `Args` only needs stating when `inputSchema` is built
|
|
112
|
-
* per request, leaving no static shape to infer from.
|
|
125
|
+
* What every tool declares, apart from `confirm` and the handler.
|
|
113
126
|
*/
|
|
114
|
-
|
|
127
|
+
interface McpxToolBase<Shape extends z.ZodRawShape> {
|
|
115
128
|
/**
|
|
116
129
|
* camelCase, unique, not one of the builtin tool names.
|
|
117
130
|
*/
|
|
@@ -133,15 +146,103 @@ export interface McpxTool<Shape extends z.ZodRawShape = z.ZodRawShape, Args = z.
|
|
|
133
146
|
* instead of stripped.
|
|
134
147
|
*/
|
|
135
148
|
inputSchema?: Shape | ((scope: McpxToolScope) => z.ZodRawShape);
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* What a handler receives.
|
|
152
|
+
*/
|
|
153
|
+
export interface McpxToolContext<Args> {
|
|
154
|
+
args: Args;
|
|
155
|
+
scope: McpxToolScope;
|
|
156
|
+
/**
|
|
157
|
+
* Shorthand for `scope.req`.
|
|
158
|
+
*/
|
|
159
|
+
req: PayloadRequest;
|
|
160
|
+
extra: McpxToolExtra;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Handed to the handler of a tool with `confirm`. Internal for now.
|
|
164
|
+
*/
|
|
165
|
+
export interface McpxConfirmation {
|
|
166
|
+
/**
|
|
167
|
+
* `true` when `runConfirmed` runs an approved call.
|
|
168
|
+
*/
|
|
169
|
+
confirmed: boolean;
|
|
170
|
+
/**
|
|
171
|
+
* Stores the call with `args` for the key's user to approve and returns
|
|
172
|
+
* the result that hands its id and URL to the client. Throws in a
|
|
173
|
+
* confirmed run.
|
|
174
|
+
*/
|
|
175
|
+
request: (args: Record<string, unknown>) => Promise<CallToolResult>;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* A tool as `defineMcpxTool` takes it. `Confirm` is inferred from `confirm`,
|
|
179
|
+
* so only a tool that sets it gets `confirmation` in its handler.
|
|
180
|
+
*/
|
|
181
|
+
export type McpxToolDefinition<Shape extends z.ZodRawShape, Args, Confirm extends McpxToolConfirm | undefined> = McpxToolBase<Shape> & {
|
|
182
|
+
/**
|
|
183
|
+
* Internal: only builtin tools are confirmable for now. The handler gets
|
|
184
|
+
* `confirmation`: a call outside a confirmed run stores itself through
|
|
185
|
+
* `confirmation.request`, the key's user approves it in the admin panel,
|
|
186
|
+
* and `runConfirmed` runs the handler again with `confirmation.confirmed`
|
|
187
|
+
* set.
|
|
188
|
+
*/
|
|
189
|
+
confirm?: Confirm;
|
|
190
|
+
handler(ctx: McpxToolContext<Args> & (Confirm extends McpxToolConfirm ? {
|
|
191
|
+
confirmation: McpxConfirmation;
|
|
192
|
+
} : unknown)): CallToolResult | Promise<CallToolResult>;
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* A tool, builtin or custom. Runs with `req.user` resolved from the key and
|
|
196
|
+
* `req.context.mcpx` set. `Args` only needs stating when `inputSchema` is built
|
|
197
|
+
* per request, leaving no static shape to infer from.
|
|
198
|
+
*/
|
|
199
|
+
export type McpxTool<Shape extends z.ZodRawShape = z.ZodRawShape, Args = z.infer<z.ZodObject<Shape>>> = McpxToolDefinition<Shape, Args, undefined> | (McpxToolDefinition<Shape, Args, McpxToolConfirm> & {
|
|
200
|
+
confirm: McpxToolConfirm;
|
|
201
|
+
});
|
|
202
|
+
/**
|
|
203
|
+
* How the admin panel shows one stored call of a confirmable tool.
|
|
204
|
+
*/
|
|
205
|
+
export interface McpxConfirmationSummary {
|
|
206
|
+
/**
|
|
207
|
+
* What the call acts on, such as a collection's label.
|
|
208
|
+
*/
|
|
209
|
+
label: string;
|
|
210
|
+
/**
|
|
211
|
+
* `undefined` when the target cannot be read.
|
|
212
|
+
*/
|
|
213
|
+
title?: string;
|
|
214
|
+
/**
|
|
215
|
+
* The target's `_status`, where it has drafts.
|
|
216
|
+
*/
|
|
217
|
+
status?: string;
|
|
218
|
+
id?: string;
|
|
219
|
+
/**
|
|
220
|
+
* Admin path that opens the target.
|
|
221
|
+
*/
|
|
222
|
+
href?: string;
|
|
223
|
+
/**
|
|
224
|
+
* The call cannot be undone.
|
|
225
|
+
*/
|
|
226
|
+
permanent: boolean;
|
|
227
|
+
/**
|
|
228
|
+
* Written by the client, so shown as such.
|
|
229
|
+
*/
|
|
230
|
+
reason?: string;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Internal for now; see {@link McpxTool.confirm}.
|
|
234
|
+
*/
|
|
235
|
+
export interface McpxToolConfirm {
|
|
236
|
+
/**
|
|
237
|
+
* Heading of the tool's group in the admin panel.
|
|
238
|
+
*/
|
|
239
|
+
label: string;
|
|
240
|
+
/**
|
|
241
|
+
* Read at view time with the access of the key's user.
|
|
242
|
+
*/
|
|
243
|
+
summarize(call: {
|
|
244
|
+
args: Record<string, unknown>;
|
|
245
|
+
}, req: PayloadRequest): Promise<McpxConfirmationSummary>;
|
|
145
246
|
}
|
|
146
247
|
/**
|
|
147
248
|
* Argument type erased, so a registry can hold tools of differing shapes.
|
|
@@ -183,8 +284,9 @@ export type McpxPluginOptions = {
|
|
|
183
284
|
*/
|
|
184
285
|
slug?: string;
|
|
185
286
|
/**
|
|
186
|
-
* Add a "Connect a client"
|
|
187
|
-
* client config. Default `true`. The snippets contain
|
|
287
|
+
* Add a "Connect a client" button to saved keys, opening a drawer with
|
|
288
|
+
* ready-to-paste MCP client config. Default `true`. The snippets contain
|
|
289
|
+
* the key in full.
|
|
188
290
|
*/
|
|
189
291
|
setupGuide?: boolean;
|
|
190
292
|
/**
|
|
@@ -224,24 +326,37 @@ export type McpxPluginOptions = {
|
|
|
224
326
|
name?: string;
|
|
225
327
|
version?: string;
|
|
226
328
|
};
|
|
329
|
+
/**
|
|
330
|
+
* Expose the server version plus repository and issue links to clients. Default `true`.
|
|
331
|
+
*/
|
|
332
|
+
diagnostics?: boolean;
|
|
227
333
|
};
|
|
228
334
|
/**
|
|
229
|
-
*
|
|
335
|
+
* One entity's operations, as the config exposes them or as a key may use
|
|
336
|
+
* them. Globals reuse this shape.
|
|
230
337
|
*/
|
|
231
|
-
export interface
|
|
338
|
+
export interface McpxEntityCapabilities {
|
|
232
339
|
read: boolean;
|
|
233
340
|
write: boolean;
|
|
234
341
|
/**
|
|
235
342
|
* Only ever true where the config lets writes publish and drafts exist.
|
|
236
343
|
*/
|
|
237
344
|
publish: boolean;
|
|
345
|
+
/**
|
|
346
|
+
* Always `false` on a global.
|
|
347
|
+
*/
|
|
348
|
+
delete: boolean;
|
|
349
|
+
/**
|
|
350
|
+
* Moves to trash without approval. Only ever true where `delete` is.
|
|
351
|
+
*/
|
|
352
|
+
deleteUnattended: boolean;
|
|
238
353
|
}
|
|
239
354
|
/**
|
|
240
355
|
* In force for one request: the plugin config and the key checkboxes together.
|
|
241
356
|
*/
|
|
242
357
|
export interface McpxResolvedCapabilities {
|
|
243
|
-
collections: Record<string,
|
|
244
|
-
globals: Record<string,
|
|
358
|
+
collections: Record<string, McpxEntityCapabilities>;
|
|
359
|
+
globals: Record<string, McpxEntityCapabilities>;
|
|
245
360
|
tools: Record<string, boolean>;
|
|
246
361
|
}
|
|
247
362
|
/**
|