@abinnovision/payloadcms-mcpx 1.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +353 -0
  3. package/dist/api-keys/collection.mjs +58 -0
  4. package/dist/api-keys/fields.mjs +137 -0
  5. package/dist/api-keys/key.mjs +10 -0
  6. package/dist/api-keys/setup-guide.mjs +54 -0
  7. package/dist/auth/resolve.mjs +62 -0
  8. package/dist/capabilities.mjs +43 -0
  9. package/dist/client/index.d.mts +2 -0
  10. package/dist/client/index.mjs +2 -0
  11. package/dist/client/setup-guide.d.mts +14 -0
  12. package/dist/client/setup-guide.mjs +87 -0
  13. package/dist/endpoint/handler.mjs +86 -0
  14. package/dist/endpoint/result.mjs +65 -0
  15. package/dist/endpoint/server.mjs +52 -0
  16. package/dist/i18n.d.mts +1 -0
  17. package/dist/i18n.mjs +40 -0
  18. package/dist/index.d.mts +5 -0
  19. package/dist/index.mjs +4 -0
  20. package/dist/options.d.mts +2 -0
  21. package/dist/options.mjs +146 -0
  22. package/dist/plugin.d.mts +9 -0
  23. package/dist/plugin.mjs +43 -0
  24. package/dist/schema/describe.mjs +160 -0
  25. package/dist/schema/lexical.d.mts +1 -0
  26. package/dist/schema/lexical.mjs +117 -0
  27. package/dist/schema/pointer.mjs +80 -0
  28. package/dist/schema/shape.mjs +208 -0
  29. package/dist/schema/walk.d.mts +3 -0
  30. package/dist/schema/walk.mjs +165 -0
  31. package/dist/tools/create-document.mjs +68 -0
  32. package/dist/tools/describe-schema.mjs +54 -0
  33. package/dist/tools/find-documents.mjs +55 -0
  34. package/dist/tools/get-document.mjs +68 -0
  35. package/dist/tools/index.mjs +23 -0
  36. package/dist/tools/list-capabilities.mjs +68 -0
  37. package/dist/tools/names.mjs +14 -0
  38. package/dist/tools/patch-document.mjs +127 -0
  39. package/dist/tools/shared.mjs +97 -0
  40. package/dist/tools/target.d.mts +3 -0
  41. package/dist/tools/target.mjs +49 -0
  42. package/dist/tools/types.d.mts +5 -0
  43. package/dist/tools/validate-document.mjs +55 -0
  44. package/dist/types.d.mts +143 -0
  45. package/dist/types.mjs +7 -0
  46. package/dist/version.mjs +6 -0
  47. package/dist/write/draft-guard.d.mts +10 -0
  48. package/dist/write/draft-guard.mjs +113 -0
  49. package/dist/write/patch.mjs +219 -0
  50. package/dist/write/publish-blockers.d.mts +15 -0
  51. package/dist/write/publish-blockers.mjs +74 -0
  52. package/dist/write/transaction.mjs +19 -0
  53. package/package.json +104 -0
@@ -0,0 +1,113 @@
1
+ import { APIError } from "payload";
2
+ import { hasDraftsEnabled } from "payload/shared";
3
+ //#region src/write/draft-guard.ts
4
+ /**
5
+ * Operation arguments that widen or redirect a write. Cleared on every MCP
6
+ * create and update so a tool cannot smuggle them in.
7
+ */ const STRIPPED_ARGS = /* @__PURE__ */ new Set([
8
+ "where",
9
+ "publishAllLocales",
10
+ "publishSpecificLocale",
11
+ "unpublishAllLocales",
12
+ "duplicateFromID",
13
+ "selectedLocales",
14
+ "overwriteExistingFiles"
15
+ ]);
16
+ /**
17
+ * Whether a request originated from the MCP endpoint. The endpoint stamps
18
+ * `req.context.mcpx`, which travels into every local API call made with the
19
+ * same `req`, including those made by custom tools.
20
+ */ const isMcpxRequest = (req) => req.context.mcpx !== void 0;
21
+ /**
22
+ * Forces every MCP write into a draft save.
23
+ *
24
+ * `draft` alone is not enough: Payload's update path only saves a draft when
25
+ * `data._status !== "published"`, so `_status` is dropped and left to Payload.
26
+ * This runs as `beforeOperation`, before Payload reads any of these arguments,
27
+ * so it holds for every create and update on an MCP request, not only the
28
+ * builtin tools. Deletes are not guarded in v1; custom tools that delete are
29
+ * the integrator's responsibility.
30
+ */ const scrubWriteArgs = (args) => {
31
+ const next = Object.fromEntries(Object.entries(args).filter(([key]) => !STRIPPED_ARGS.has(key)));
32
+ if (next["data"] && typeof next["data"] === "object") {
33
+ const { _status: _ignoredStatus, deletedAt: _ignoredDeletedAt, ...data } = next["data"];
34
+ next["data"] = data;
35
+ }
36
+ next["draft"] = true;
37
+ next["autosave"] = false;
38
+ next["overrideLock"] = false;
39
+ next["trash"] = false;
40
+ return next;
41
+ };
42
+ const forceDraftWrite = (hookArgs) => {
43
+ const { args, operation, req } = hookArgs;
44
+ if (!isMcpxRequest(req) || operation !== "create" && operation !== "update") return args;
45
+ return scrubWriteArgs(args);
46
+ };
47
+ /**
48
+ * The global counterpart of {@link forceDraftWrite}. Payload invokes a global's
49
+ * `beforeOperation` with the whole argument bag and assigns the result back,
50
+ * exactly as the collection path does and before it reads `draft`,
51
+ * `publishAllLocales` or `data._status`, so the guard has the same reach here:
52
+ * every MCP write to a global, builtin tool or custom.
53
+ *
54
+ * The global operation union has no `create` member because a global always
55
+ * exists, so only `update` is intercepted. `STRIPPED_ARGS` covers the three
56
+ * publish vectors `updateGlobal` accepts; the rest of the set does not exist on
57
+ * that signature and filtering it is a harmless no-op. `slug` survives the
58
+ * filter, so the operation still knows what it is updating.
59
+ */ const forceDraftWriteGlobal = (hookArgs) => {
60
+ const { operation, req } = hookArgs;
61
+ const args = hookArgs.args;
62
+ if (!isMcpxRequest(req) || operation !== "update") return args;
63
+ return scrubWriteArgs(args);
64
+ };
65
+ /**
66
+ * Refuses an MCP write that would still not land as a draft. An alarm rather
67
+ * than the guarantee: `forceDraftWrite` should make it unreachable. It throws
68
+ * instead of correcting `_status` because Payload has already chosen the write
69
+ * branch by the time a `beforeChange` hook runs.
70
+ */ const refuseUnlessDraft = (req, slug, data) => {
71
+ if (!isMcpxRequest(req)) return;
72
+ const status = data._status;
73
+ if (status === "draft") return;
74
+ req.payload.logger.warn(`[payloadcms-mcpx] Refused a write to ${slug} that would not have been a draft (_status: ${String(status)}).`);
75
+ throw new APIError("MCP clients may only write drafts. This write was refused because it would not have been saved as one.", 403);
76
+ };
77
+ const refusePublish = ({ collection, data, req }) => {
78
+ refuseUnlessDraft(req, collection.slug, data);
79
+ return data;
80
+ };
81
+ /** The global counterpart of {@link refusePublish}. */ const refusePublishGlobal = ({ data, global, req }) => {
82
+ const next = data;
83
+ refuseUnlessDraft(req, global.slug, next);
84
+ return next;
85
+ };
86
+ /**
87
+ * Attaches the draft guard to every collection: `forceDraftWrite` everywhere
88
+ * (it is a no-op outside MCP requests) and `refusePublish` wherever drafts
89
+ * exist. Applied to the built collection list so nothing can join later
90
+ * without being covered.
91
+ */ const installDraftGuards = (collections) => collections.map((collection) => ({
92
+ ...collection,
93
+ hooks: {
94
+ ...collection.hooks,
95
+ beforeOperation: [...collection.hooks?.beforeOperation ?? [], forceDraftWrite],
96
+ ...hasDraftsEnabled(collection) ? { beforeChange: [...collection.hooks?.beforeChange ?? [], refusePublish] } : {}
97
+ }
98
+ }));
99
+ /**
100
+ * Attaches the guard to every global, exposed or not, for the same reason
101
+ * `installDraftGuards` covers every collection: a custom tool running on an MCP
102
+ * request must not be able to publish through a global the plugin config never
103
+ * mentioned.
104
+ */ const installGlobalDraftGuards = (globals) => globals.map((global) => ({
105
+ ...global,
106
+ hooks: {
107
+ ...global.hooks,
108
+ beforeOperation: [...global.hooks?.beforeOperation ?? [], forceDraftWriteGlobal],
109
+ ...hasDraftsEnabled(global) ? { beforeChange: [...global.hooks?.beforeChange ?? [], refusePublishGlobal] } : {}
110
+ }
111
+ }));
112
+ //#endregion
113
+ export { forceDraftWrite, forceDraftWriteGlobal, installDraftGuards, installGlobalDraftGuards, isMcpxRequest, refusePublish, refusePublishGlobal };
@@ -0,0 +1,219 @@
1
+ import { JSON_POINTER_PATTERN, RESERVED_FIELD_NAMES, blockOf, describeAddressableFields, findBlocksField, splitPath } from "../schema/walk.mjs";
2
+ import { validateWriteValue } from "../schema/shape.mjs";
3
+ import { resolveDataPointer } from "../schema/pointer.mjs";
4
+ import { z } from "zod";
5
+ import { Pointer, applyPatch } from "rfc6902";
6
+ //#region src/write/patch.ts
7
+ /**
8
+ * One RFC 6902 operation as accepted by `patchDocument`.
9
+ */ const PATCH_OPERATION_SCHEMA = z.object({
10
+ from: z.string().regex(JSON_POINTER_PATTERN).optional(),
11
+ op: z.enum([
12
+ "add",
13
+ "copy",
14
+ "move",
15
+ "remove",
16
+ "replace",
17
+ "test"
18
+ ]),
19
+ path: z.string().regex(JSON_POINTER_PATTERN),
20
+ value: z.unknown().optional()
21
+ }).describe("An RFC 6902 operation.");
22
+ /**
23
+ * Whether a pointer touches a field Payload maintains.
24
+ */ const isReservedPointer = (pointer) => pointer.split("/").slice(1).some((segment) => RESERVED_FIELD_NAMES.has(segment));
25
+ /**
26
+ * The pointer an operation removes a value from, if it removes one at all.
27
+ */ const droppedPointer = (operation) => {
28
+ if (operation.op === "remove") return operation.path;
29
+ return operation.op === "move" ? operation.from : void 0;
30
+ };
31
+ /**
32
+ * Whether a pointer addresses a list element rather than a field.
33
+ */ const isElementPointer = (pointer) => {
34
+ const last = pointer.split("/").pop() ?? "";
35
+ return last === "-" || /^\d+$/.test(last);
36
+ };
37
+ const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
38
+ /**
39
+ * A Lexical editor state. Its nodes manage their own ids, so it is never
40
+ * descended into.
41
+ */ const isRichTextState = (value) => isPlainObject(value["root"]) && Array.isArray(value["root"]["children"]);
42
+ /**
43
+ * Visits every row in a value: a plain object that carries `blockType` or
44
+ * sits directly inside an array. Rich text states manage their own nodes and
45
+ * are never descended into.
46
+ */ const walkRows = (value, visit, isRow = false) => {
47
+ if (Array.isArray(value)) {
48
+ for (const entry of value) walkRows(entry, visit, true);
49
+ return;
50
+ }
51
+ if (!isPlainObject(value) || isRichTextState(value)) return;
52
+ if (isRow || typeof value["blockType"] === "string") visit(value);
53
+ for (const entry of Object.values(value)) walkRows(entry, visit);
54
+ };
55
+ /**
56
+ * Keeps a row id only when the stored document already has it and no earlier
57
+ * row in the write claimed it; every other id is dropped so Payload assigns a
58
+ * fresh one.
59
+ *
60
+ * A kept id makes Payload update the row in place, which preserves the other
61
+ * locales of any localized field inside it. A duplicated id (a copied row) or
62
+ * an id from elsewhere would violate a SQL primary key, so those never pass.
63
+ */ const reconcileRowIds = (next, stored) => {
64
+ const known = /* @__PURE__ */ new Set();
65
+ walkRows(stored, (row) => {
66
+ if (row["id"] !== void 0) known.add(row["id"]);
67
+ });
68
+ const seen = /* @__PURE__ */ new Set();
69
+ walkRows(next, (row) => {
70
+ const id = row["id"];
71
+ if (id === void 0) return;
72
+ if (known.has(id) && !seen.has(id)) {
73
+ seen.add(id);
74
+ return;
75
+ }
76
+ delete row["id"];
77
+ });
78
+ };
79
+ /**
80
+ * Drops every row id from a copy of `value`. Used on create, where no stored
81
+ * row exists and any incoming id is client-invented.
82
+ */ const stripRowIds = (value) => {
83
+ const next = structuredClone(value);
84
+ walkRows(next, (row) => {
85
+ delete row["id"];
86
+ });
87
+ return next;
88
+ };
89
+ /**
90
+ * Applies a patch to a deep copy of the document, so a failing operation
91
+ * leaves the original untouched and nothing partial is ever written.
92
+ */ const applyPatchToCopy = (doc, patches) => {
93
+ const next = structuredClone(doc);
94
+ const prepared = patches.map((operation) => {
95
+ const cloned = "value" in operation ? {
96
+ ...operation,
97
+ value: structuredClone(operation.value)
98
+ } : operation;
99
+ if (cloned.op === "replace" && !isElementPointer(cloned.path) && Pointer.fromJSON(cloned.path).get(next) === void 0) return {
100
+ ...cloned,
101
+ op: "add"
102
+ };
103
+ return cloned;
104
+ });
105
+ const problems = applyPatch(next, prepared).flatMap((error, index) => error ? [`patches[${String(index)}]: ${error.message}`] : []);
106
+ if (problems.length > 0) return { problems };
107
+ reconcileRowIds(next, doc);
108
+ return { next };
109
+ };
110
+ /**
111
+ * Checks every operation against the schema before any is applied.
112
+ *
113
+ * A partially applied batch is worse than a refused one, so this returns all
114
+ * problems and the caller applies nothing unless the list is empty.
115
+ */ const findPatchProblems = (config, target) => target.patches.flatMap((operation, index) => {
116
+ const at = `patches[${String(index)}]`;
117
+ const pointers = [operation.path, ..."from" in operation && operation.from ? [operation.from] : []];
118
+ if (pointers.includes("")) return [`${at}: an empty pointer addresses the whole document. Address a field instead.`];
119
+ const reserved = pointers.find(isReservedPointer);
120
+ if (reserved !== void 0) return [`${at}: "${reserved}" addresses a field Payload maintains. Drafts are the only thing this tool writes, and id, _status, createdAt and updatedAt are not writable.`];
121
+ const dropped = droppedPointer(operation);
122
+ if (dropped !== void 0 && !isElementPointer(dropped)) return [`${at}: "${dropped}" is a field, not a list element, and removing it would do nothing. The patched document is written whole, and Payload keeps any field absent from a write rather than clearing it. Use "replace" with null to clear a field, or with [] to empty a list.`];
123
+ const value = "value" in operation ? operation.value : void 0;
124
+ try {
125
+ const moved = "from" in operation && operation.from ? Pointer.fromJSON(operation.from).get(target.doc) : void 0;
126
+ for (const pointer of pointers) {
127
+ const resolution = resolveDataPointer(config, {
128
+ addedValue: value ?? moved,
129
+ doc: target.doc,
130
+ pointer,
131
+ ref: target.ref
132
+ });
133
+ if (pointer === operation.path && value !== void 0) return validateWriteValue(config, {
134
+ pointer,
135
+ resolution
136
+ }, value).map((problem) => `${at}: ${problem}`);
137
+ }
138
+ return [];
139
+ } catch (error) {
140
+ return [`${at}: ${error instanceof Error ? error.message : "invalid"}`];
141
+ }
142
+ });
143
+ /**
144
+ * Keys Payload manages on a row that travel back into the write unchanged.
145
+ */ const ROW_KEYS = /* @__PURE__ */ new Set([
146
+ "blockName",
147
+ "blockType",
148
+ "id"
149
+ ]);
150
+ /**
151
+ * Picks the keys the schema walker describes out of `value`, descending into
152
+ * groups, named tabs, arrays and blocks. Everything Payload maintains or
153
+ * derives (`_status`, timestamps, join and virtual fields, upload base fields)
154
+ * is left out, so the write-back carries only what a client could have set.
155
+ */ const pickDescribed = (config, value, at) => {
156
+ const { fields, prefix, isRow } = at;
157
+ const relative = describeAddressableFields(fields).flatMap((descriptor) => {
158
+ const parts = splitPath(descriptor.path);
159
+ return prefix.every((part, offset) => part === parts[offset]) ? [{
160
+ descriptor,
161
+ parts: parts.slice(prefix.length)
162
+ }] : [];
163
+ });
164
+ const result = {};
165
+ if (isRow) {
166
+ for (const key of ROW_KEYS) if (key in value) result[key] = value[key];
167
+ }
168
+ for (const [key, entry] of Object.entries(value)) {
169
+ const candidates = relative.filter(({ parts }) => parts[0] === key);
170
+ if (candidates.length === 0) continue;
171
+ const exact = candidates.find(({ parts }) => parts.length === 1);
172
+ if (exact?.descriptor.type === "blocks" && Array.isArray(entry)) {
173
+ const field = findBlocksField(fields, splitPath(exact.descriptor.path));
174
+ result[key] = entry.map((row) => {
175
+ const block = field && isPlainObject(row) && typeof row["blockType"] === "string" ? blockOf(config, field, row["blockType"]) : void 0;
176
+ return block && isPlainObject(row) ? pickDescribed(config, row, {
177
+ fields: block.flattenedFields,
178
+ prefix: [],
179
+ isRow: true
180
+ }) : row;
181
+ });
182
+ continue;
183
+ }
184
+ if (exact) {
185
+ result[key] = entry;
186
+ continue;
187
+ }
188
+ if (candidates.some(({ parts }) => parts[1] === "*")) {
189
+ result[key] = Array.isArray(entry) ? entry.map((row) => isPlainObject(row) ? pickDescribed(config, row, {
190
+ fields,
191
+ prefix: [
192
+ ...prefix,
193
+ key,
194
+ "*"
195
+ ],
196
+ isRow: true
197
+ }) : row) : entry;
198
+ continue;
199
+ }
200
+ result[key] = isPlainObject(entry) ? pickDescribed(config, entry, {
201
+ fields,
202
+ prefix: [...prefix, key],
203
+ isRow: false
204
+ }) : entry;
205
+ }
206
+ return result;
207
+ };
208
+ /**
209
+ * The data handed to `payload.update` after a patch: the patched document
210
+ * reduced to the fields the client may write, plus row identity keys.
211
+ */ const buildWriteData = (config, target, doc) => {
212
+ return pickDescribed(config, doc, {
213
+ fields: target.flattenedFields,
214
+ prefix: [],
215
+ isRow: false
216
+ });
217
+ };
218
+ //#endregion
219
+ export { PATCH_OPERATION_SCHEMA, applyPatchToCopy, buildWriteData, droppedPointer, findPatchProblems, isElementPointer, isReservedPointer, stripRowIds };
@@ -0,0 +1,15 @@
1
+ import "../tools/target.mjs";
2
+ import { PayloadRequest } from "payload";
3
+ //#region src/write/publish-blockers.d.ts
4
+ /**
5
+ * One reason a human could not publish the draft as it stands.
6
+ */
7
+ interface PublishBlocker {
8
+ /** Resolved field label path, e.g. "Layout > Block 2 (Hero) > Title". */
9
+ field?: string;
10
+ message: string;
11
+ /** JSON Pointer to the offending value, e.g. "/layout/2/title". */
12
+ path: string;
13
+ }
14
+ //#endregion
15
+ export type { PublishBlocker };
@@ -0,0 +1,74 @@
1
+ import { pointerFromPayloadPath } from "../schema/walk.mjs";
2
+ import { beforeChangeTraverseFields, beforeValidateTraverseFields } from "payload";
3
+ //#region src/write/publish-blockers.ts
4
+ /**
5
+ * Runs Payload's own field validation over a draft without saving anything.
6
+ *
7
+ * Draft saves skip validation unless `versions.drafts.validate` is set, so an
8
+ * agent building a document incrementally gets no signal until a human presses
9
+ * Publish. This is the same traversal a real save runs, exported from
10
+ * `payload`, called with `skipValidation: false` so it collects into `errors`
11
+ * instead of throwing. The `beforeValidate` pass runs first because some field
12
+ * hooks (Lexical's) prepare state in `context` that their `beforeChange`
13
+ * counterpart depends on.
14
+ *
15
+ * Nothing is written: `data` is a copy, the context is a scratch copy and the
16
+ * locale merge actions are discarded. `overrideAccess` is true because the
17
+ * question is "could this be published", not "may this client write it".
18
+ *
19
+ * Limits: only the locale the doc was read in is checked, and field-level
20
+ * `beforeChange` hooks run again, which is safe only for pure ones.
21
+ */ const collectPublishBlockers = async (req, target) => {
22
+ const { doc, entity } = target;
23
+ const id = doc["id"];
24
+ const errors = [];
25
+ const data = {
26
+ ...structuredClone(doc),
27
+ _status: "published"
28
+ };
29
+ const context = { ...req.context };
30
+ const shared = {
31
+ collection: entity.kind === "collection" ? entity.config : null,
32
+ context,
33
+ data,
34
+ doc,
35
+ global: entity.kind === "global" ? entity.config : null,
36
+ operation: "update",
37
+ overrideAccess: true,
38
+ parentIndexPath: "",
39
+ parentIsLocalized: false,
40
+ parentPath: "",
41
+ parentSchemaPath: "",
42
+ req,
43
+ siblingDoc: doc,
44
+ ...id === void 0 ? {} : { id }
45
+ };
46
+ try {
47
+ await beforeValidateTraverseFields({
48
+ ...shared,
49
+ fields: entity.config.fields,
50
+ siblingData: data
51
+ });
52
+ await beforeChangeTraverseFields({
53
+ ...shared,
54
+ docWithLocales: doc,
55
+ errors,
56
+ fieldLabelPath: "",
57
+ fields: entity.config.fields,
58
+ mergeLocaleActions: [],
59
+ siblingData: data,
60
+ siblingDocWithLocales: doc,
61
+ skipValidation: false
62
+ });
63
+ } catch (error) {
64
+ req.payload.logger.warn(`[payloadcms-mcpx] Could not validate the ${entity.slug} draft: ${error instanceof Error ? error.message : "unknown error"}`);
65
+ return [];
66
+ }
67
+ return errors.map((error) => ({
68
+ message: error.message,
69
+ path: pointerFromPayloadPath(error.path),
70
+ ...typeof error.label === "string" ? { field: error.label } : {}
71
+ }));
72
+ };
73
+ //#endregion
74
+ export { collectPublishBlockers };
@@ -0,0 +1,19 @@
1
+ import { commitTransaction, initTransaction, killTransaction } from "payload";
2
+ //#region src/write/transaction.ts
3
+ /**
4
+ * Runs `fn` inside one database transaction on `req`, so a read followed by a
5
+ * write cannot interleave with another writer. Adapters without transaction
6
+ * support, or a request that already owns one, run `fn` as is.
7
+ */ const withTransaction = async (req, fn) => {
8
+ if (!await initTransaction(req)) return await fn();
9
+ try {
10
+ const result = await fn();
11
+ await commitTransaction(req);
12
+ return result;
13
+ } catch (error) {
14
+ await killTransaction(req);
15
+ throw error;
16
+ }
17
+ };
18
+ //#endregion
19
+ export { withTransaction };
package/package.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "name": "@abinnovision/payloadcms-mcpx",
4
+ "version": "1.0.0-beta.10",
5
+ "description": "Payload CMS plugin exposing a fixed, schema-aware MCP tool surface with draft-only writes and per-API-key capabilities.",
6
+ "keywords": [
7
+ "payload",
8
+ "payloadcms",
9
+ "plugin",
10
+ "mcp",
11
+ "model-context-protocol"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/abinnovision/payloadcms-commons.git",
16
+ "directory": "packages/mcpx"
17
+ },
18
+ "license": "Apache-2.0",
19
+ "author": {
20
+ "name": "abi group GmbH",
21
+ "email": "info@abigroup.io",
22
+ "url": "https://abigroup.io"
23
+ },
24
+ "type": "module",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.mts",
28
+ "default": "./dist/index.mjs"
29
+ },
30
+ "./client": {
31
+ "types": "./dist/client/index.d.mts",
32
+ "default": "./dist/client/index.mjs"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "LICENSE",
38
+ "README.md"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsdown",
42
+ "format:check": "prettier --check 'src/**/*.{ts,tsx}' 'test/**/*.ts' '*.{json{,5},md,y{,a}ml}'",
43
+ "format:fix": "prettier --write 'src/**/*.{ts,tsx}' 'test/**/*.ts' '*.{json{,5},md,y{,a}ml}'",
44
+ "lint:check": "eslint 'src/**/*.{ts,tsx}' 'test/**/*.ts'",
45
+ "lint:fix": "eslint 'src/**/*.{ts,tsx}' 'test/**/*.ts' --fix",
46
+ "test-integration": "vitest --run --config test/integration/vitest.config.mts",
47
+ "test-unit": "vitest --run --coverage --config vitest.config.mts",
48
+ "test-unit:watch": "vitest --config vitest.config.mts",
49
+ "typecheck": "tsc --noEmit"
50
+ },
51
+ "lint-staged": {
52
+ "{src,test}/**/*.{ts,tsx}": [
53
+ "eslint --fix",
54
+ "prettier --write"
55
+ ],
56
+ "*.{json{,5},md,y{,a}ml}": "prettier --write"
57
+ },
58
+ "prettier": "@abinnovision/prettier-config",
59
+ "dependencies": {
60
+ "@modelcontextprotocol/sdk": "^1.30.0",
61
+ "rfc6902": "^5.3.0",
62
+ "zod": "^4.4.3"
63
+ },
64
+ "devDependencies": {
65
+ "@abinnovision/eslint-config-base": "^3.4.2",
66
+ "@abinnovision/prettier-config": "^2.2.0",
67
+ "@arethetypeswrong/core": "^0.18.5",
68
+ "@payloadcms/db-sqlite": "3.88.0",
69
+ "@payloadcms/richtext-lexical": "3.88.0",
70
+ "@payloadcms/ui": "3.88.0",
71
+ "@swc/core": "^1.16.1",
72
+ "@types/node": "^26.2.0",
73
+ "@types/react": "^19.2.18",
74
+ "@vitest/coverage-v8": "^4.1.10",
75
+ "eslint": "^10.7.0",
76
+ "payload": "3.88.0",
77
+ "prettier": "^3.9.5",
78
+ "publint": "^0.3.24",
79
+ "react": "^19.2.8",
80
+ "react-dom": "^19.2.8",
81
+ "tsdown": "^0.22.14",
82
+ "typescript": "^6.0.3",
83
+ "unplugin-swc": "^1.5.11",
84
+ "vitest": "^4.1.10"
85
+ },
86
+ "peerDependencies": {
87
+ "@payloadcms/ui": ">=3.88.0 <4",
88
+ "payload": ">=3.88.0 <4",
89
+ "react": "^19"
90
+ },
91
+ "peerDependenciesMeta": {
92
+ "@payloadcms/ui": {
93
+ "optional": true
94
+ },
95
+ "react": {
96
+ "optional": true
97
+ }
98
+ },
99
+ "publishConfig": {
100
+ "ghpr": true,
101
+ "npm": true,
102
+ "npmAccess": "public"
103
+ }
104
+ }