@uniformdev/uniform-mcp 20.14.2-alpha.35
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/LICENSE.txt +2 -0
- package/README.md +49 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +618 -0
- package/package.json +44 -0
package/LICENSE.txt
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
## Uniform MCP Server
|
|
2
|
+
|
|
3
|
+
A stdio-transport MCP server which allows you to manipulate Uniform entities.
|
|
4
|
+
|
|
5
|
+
Part of the [Uniform Platform](https://uniform.app). See our [documentation](https://docs.uniform.app) for more details.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Setup
|
|
9
|
+
|
|
10
|
+
#### Uniform Context
|
|
11
|
+
|
|
12
|
+
Copy `references/*.mdc` to your project and set it to be referenced as you wish.
|
|
13
|
+
For Cursor, this belongs in `.cursor/rules/*.mdc`.
|
|
14
|
+
|
|
15
|
+
#### Register MCP server
|
|
16
|
+
|
|
17
|
+
* Add the following to `.cursor/mcp.json` (for other editors, this should be easily convertible)
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"Uniform": {
|
|
23
|
+
"type": "stdio",
|
|
24
|
+
"command": "npx",
|
|
25
|
+
"args": ["-y", "@uniformdev/uniform-mcp"],
|
|
26
|
+
"env": {
|
|
27
|
+
"UNIFORM_API_KEY": "your-api-key",
|
|
28
|
+
"UNIFORM_PROJECT_ID": "your-project-id",
|
|
29
|
+
// optional, only needed if your host is not uniform.app (e.g. EU users)
|
|
30
|
+
"UNIFORM_API_HOST": "https://uniform.app",
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
* Enable the MCP server if necessary.
|
|
39
|
+
|
|
40
|
+
Note: if you are developing against this mcp server, you must use `node` with args `../path/to/this-package-source/dist/index.js` - npx does not support local packages. If your Uniform API host is self-signed, `NODE_TLS_REJECT_UNAUTHORIZED: 0` env var works, but do not use in production.
|
|
41
|
+
|
|
42
|
+
## Notable Limitations
|
|
43
|
+
|
|
44
|
+
Pattern editing supports a limited subset of patterns. Specifically:
|
|
45
|
+
|
|
46
|
+
- Locale support is rudimentary (cannot enable or disable locales, but can change existing localized param values)
|
|
47
|
+
- Conditional values are not supported
|
|
48
|
+
- Inserting other nested patterns is not supported
|
|
49
|
+
- Editing overrides is not supported
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
|
|
5
|
+
// src/tools/componentPattern/componentPatternAction.ts
|
|
6
|
+
import {
|
|
7
|
+
CANVAS_DRAFT_STATE,
|
|
8
|
+
CanvasClient,
|
|
9
|
+
walkNodeTree
|
|
10
|
+
} from "@uniformdev/canvas";
|
|
11
|
+
import { produce } from "immer";
|
|
12
|
+
import { z as z3 } from "zod";
|
|
13
|
+
|
|
14
|
+
// src/env.ts
|
|
15
|
+
var apiKey = process.env.UNIFORM_API_KEY;
|
|
16
|
+
var projectId = process.env.UNIFORM_PROJECT_ID;
|
|
17
|
+
var apiHost = process.env.UNIFORM_API_HOST ?? "https://uniform.app";
|
|
18
|
+
if (!apiKey) {
|
|
19
|
+
throw new Error("UNIFORM_API_KEY is not set");
|
|
20
|
+
}
|
|
21
|
+
if (!projectId) {
|
|
22
|
+
throw new Error("UNIFORM_PROJECT_ID is not set");
|
|
23
|
+
}
|
|
24
|
+
var env = {
|
|
25
|
+
apiHost,
|
|
26
|
+
apiKey,
|
|
27
|
+
projectId,
|
|
28
|
+
baseWebUrl: `${apiHost}/projects/${projectId}`
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/tools/componentPattern/util.ts
|
|
32
|
+
import { z as z2 } from "zod";
|
|
33
|
+
|
|
34
|
+
// src/properties.ts
|
|
35
|
+
import { z } from "zod";
|
|
36
|
+
var propertyDefinitionSchema = z.object({
|
|
37
|
+
name: z.string().describe('Title-cased display name. Ex: "Headline"'),
|
|
38
|
+
id: z.string().describe("The public ID. Must be unique within the current entity fields/parameters."),
|
|
39
|
+
type: z.enum([
|
|
40
|
+
"text",
|
|
41
|
+
"richText",
|
|
42
|
+
"link",
|
|
43
|
+
"asset",
|
|
44
|
+
"number",
|
|
45
|
+
"date",
|
|
46
|
+
"dateTime",
|
|
47
|
+
"checkbox",
|
|
48
|
+
"reference",
|
|
49
|
+
"select"
|
|
50
|
+
]),
|
|
51
|
+
guidance: z.string().describe(
|
|
52
|
+
'Describe what kind of content should be populated, for a LLM to use as a guide. 1-2 sentences max. Ex: "Meta tags based on the content", "2-3 word headline"'
|
|
53
|
+
),
|
|
54
|
+
localizable: z.boolean().describe("Whether values are stored as locale-specific"),
|
|
55
|
+
typeConfig: z.object({
|
|
56
|
+
allowedTypes: z.array(z.enum(["image", "video", "audio", "other"])).optional().describe("The types of asset allowed. ONLY applies when type=asset, omit for other types"),
|
|
57
|
+
options: z.array(
|
|
58
|
+
z.object({
|
|
59
|
+
text: z.string().describe("The text to display to the author"),
|
|
60
|
+
value: z.string().describe("The value to store for the option")
|
|
61
|
+
})
|
|
62
|
+
).optional().describe(
|
|
63
|
+
"The author-selectable options for the select property. ONLY applies when type=select, omit for other types"
|
|
64
|
+
),
|
|
65
|
+
required: z.boolean()
|
|
66
|
+
}).optional()
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// src/tools/componentPattern/util.ts
|
|
70
|
+
var aiContentTypeDefinitionSchema = z2.object({
|
|
71
|
+
name: z2.string().describe('Title-cased display name of the component. Ex: "Hero"'),
|
|
72
|
+
id: z2.string().describe(
|
|
73
|
+
"Public ID of the content type, the camelCase version of name, using only alphanumeric characters"
|
|
74
|
+
),
|
|
75
|
+
description: z2.string().describe("Describe the purpose of the content type in 1-2 sentences."),
|
|
76
|
+
fields: z2.array(propertyDefinitionSchema).describe("The fields of the content type. The key is the publicId of the field.").optional()
|
|
77
|
+
});
|
|
78
|
+
function linkToComponentPattern(id) {
|
|
79
|
+
return `${env.baseWebUrl}/dashboards/canvas/edit/${id}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/tools/componentPattern/componentPatternAction.ts
|
|
83
|
+
var componentPatternActionSchema = {
|
|
84
|
+
action: z3.enum(["create", "update", "delete"]).describe("The action to perform"),
|
|
85
|
+
patternUuid: z3.string().uuid().describe("UUID of the pattern instance to create, update, or delete"),
|
|
86
|
+
name: z3.string().describe(
|
|
87
|
+
"Name of the pattern. If the name does not need to be changed, omit this field. Always include for creates."
|
|
88
|
+
).optional(),
|
|
89
|
+
patternType: z3.string().describe(
|
|
90
|
+
"Component definition type of the pattern. Must be registered with Uniform. Always include for creates. Omit otherwise."
|
|
91
|
+
).optional(),
|
|
92
|
+
newComponentsInSlots: z3.array(
|
|
93
|
+
z3.object({
|
|
94
|
+
parentComponentId: z3.string().describe("ID of the component the slot belongs to"),
|
|
95
|
+
slotName: z3.string().describe("Public ID of the target slot"),
|
|
96
|
+
slotIndex: z3.number().describe("Index to insert the new component at. Omit for append.").optional(),
|
|
97
|
+
componentType: z3.string().describe(
|
|
98
|
+
"Public ID of the component type to insert. Component type must be registered with Uniform."
|
|
99
|
+
),
|
|
100
|
+
newComponentInstanceUuid: z3.string().uuid().describe("ID of the new component to create - generate a random unique UUID")
|
|
101
|
+
})
|
|
102
|
+
).describe(
|
|
103
|
+
"New components to insert in slots. Omit for delete. These are performed before deleting components or parameter updates."
|
|
104
|
+
).optional(),
|
|
105
|
+
deletedComponentsInSlots: z3.array(z3.string().describe("Component ID to remove. Cannot be the pattern root component.")).describe(
|
|
106
|
+
"Components to delete from slots during an update. Omit for create or delete, or if nothing needs deleting. These are performed before parameter updates."
|
|
107
|
+
).optional(),
|
|
108
|
+
parameterUpdates: z3.array(
|
|
109
|
+
z3.object({
|
|
110
|
+
componentId: z3.string().describe("ID of the component the parameter belongs to"),
|
|
111
|
+
parameterId: z3.string().describe(
|
|
112
|
+
"Public ID of the parameter to update. Must be defined in the component type of the parent componentId"
|
|
113
|
+
),
|
|
114
|
+
parameterType: z3.string().describe("The type of the parameterId as defined on the component definition"),
|
|
115
|
+
locale: z3.string().describe("Locale of the parameter value. Omit for non-localized values.").optional(),
|
|
116
|
+
value: z3.any().describe("New value for the parameter. Use null to remove an existing value.")
|
|
117
|
+
}).describe("A value change to a parameter. Omit unchanged parameters.")
|
|
118
|
+
).describe("Updates to the parameters of the pattern. Omit for delete.").optional()
|
|
119
|
+
};
|
|
120
|
+
function registerComponentPatternAction(server2) {
|
|
121
|
+
server2.tool(
|
|
122
|
+
"componentPatternAction",
|
|
123
|
+
"Creates, updates, or deletes a Uniform Component Pattern or Composition Pattern",
|
|
124
|
+
componentPatternActionSchema,
|
|
125
|
+
async ({
|
|
126
|
+
action,
|
|
127
|
+
patternUuid,
|
|
128
|
+
name,
|
|
129
|
+
patternType,
|
|
130
|
+
deletedComponentsInSlots,
|
|
131
|
+
newComponentsInSlots,
|
|
132
|
+
parameterUpdates
|
|
133
|
+
}) => {
|
|
134
|
+
const canvasClient = new CanvasClient(env);
|
|
135
|
+
if (action === "create") {
|
|
136
|
+
if (!name) {
|
|
137
|
+
throw new Error("Name is required for create");
|
|
138
|
+
}
|
|
139
|
+
if (!patternType) {
|
|
140
|
+
throw new Error("Pattern type is required for create");
|
|
141
|
+
}
|
|
142
|
+
const coreComposition = {
|
|
143
|
+
projectId: env.projectId,
|
|
144
|
+
state: CANVAS_DRAFT_STATE,
|
|
145
|
+
pattern: true,
|
|
146
|
+
composition: {
|
|
147
|
+
_id: patternUuid,
|
|
148
|
+
_name: name,
|
|
149
|
+
type: patternType
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const composition = applyCompositionPatches(
|
|
153
|
+
coreComposition,
|
|
154
|
+
deletedComponentsInSlots ?? [],
|
|
155
|
+
newComponentsInSlots ?? [],
|
|
156
|
+
parameterUpdates ?? [],
|
|
157
|
+
name
|
|
158
|
+
);
|
|
159
|
+
await canvasClient.updateComposition(composition);
|
|
160
|
+
}
|
|
161
|
+
if (action === "update") {
|
|
162
|
+
const existingComposition = await canvasClient.getCompositionById({
|
|
163
|
+
compositionId: patternUuid,
|
|
164
|
+
state: CANVAS_DRAFT_STATE,
|
|
165
|
+
skipDataResolution: true,
|
|
166
|
+
skipPatternResolution: true,
|
|
167
|
+
withComponentIDs: true,
|
|
168
|
+
skipOverridesResolution: true
|
|
169
|
+
});
|
|
170
|
+
const composition = applyCompositionPatches(
|
|
171
|
+
existingComposition,
|
|
172
|
+
deletedComponentsInSlots ?? [],
|
|
173
|
+
newComponentsInSlots ?? [],
|
|
174
|
+
parameterUpdates ?? [],
|
|
175
|
+
name
|
|
176
|
+
);
|
|
177
|
+
await canvasClient.updateComposition(composition);
|
|
178
|
+
}
|
|
179
|
+
if (action === "delete") {
|
|
180
|
+
await canvasClient.removeComposition({ compositionId: patternUuid });
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: JSON.stringify({ success: true, editUrl: linkToComponentPattern(patternUuid) }, null, 2)
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
function applyCompositionPatches(existingComposition, deletedComponentsInSlots, newComponentsInSlots, parameterUpdates, nameUpdate) {
|
|
194
|
+
return produce(existingComposition, (draft) => {
|
|
195
|
+
if (nameUpdate) {
|
|
196
|
+
draft.composition._name = nameUpdate;
|
|
197
|
+
}
|
|
198
|
+
const remainingInsertions = newComponentsInSlots.reduce(
|
|
199
|
+
(acc, newComponent) => {
|
|
200
|
+
acc[newComponent.newComponentInstanceUuid] = newComponent;
|
|
201
|
+
return acc;
|
|
202
|
+
},
|
|
203
|
+
{}
|
|
204
|
+
);
|
|
205
|
+
for (const newComponent of newComponentsInSlots ?? []) {
|
|
206
|
+
walkNodeTree(draft.composition, ({ node, type }) => {
|
|
207
|
+
if (type !== "component" || node._id !== newComponent.parentComponentId) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
node.slots ??= {};
|
|
211
|
+
node.slots[newComponent.slotName] ??= [];
|
|
212
|
+
node.slots[newComponent.slotName].splice(
|
|
213
|
+
newComponent.slotIndex ?? node.slots[newComponent.slotName].length,
|
|
214
|
+
0,
|
|
215
|
+
{
|
|
216
|
+
_id: newComponent.newComponentInstanceUuid,
|
|
217
|
+
type: newComponent.componentType
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
delete remainingInsertions[newComponent.newComponentInstanceUuid];
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
if (Object.keys(remainingInsertions).length > 0) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`New components with invalid parent component IDs were not inserted: ${Object.keys(remainingInsertions).join(", ")}`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
const remainingDeletions = deletedComponentsInSlots.reduce(
|
|
229
|
+
(acc, deletedComponent) => {
|
|
230
|
+
acc[deletedComponent] = deletedComponent;
|
|
231
|
+
return acc;
|
|
232
|
+
},
|
|
233
|
+
{}
|
|
234
|
+
);
|
|
235
|
+
for (const deletedComponent of deletedComponentsInSlots ?? []) {
|
|
236
|
+
walkNodeTree(draft.composition, ({ node, actions }) => {
|
|
237
|
+
if (node._id !== deletedComponent) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
actions.remove();
|
|
241
|
+
delete remainingDeletions[deletedComponent];
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (Object.keys(remainingDeletions).length > 0) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`Components with invalid IDs were not deleted: ${Object.keys(remainingDeletions).join(", ")}`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
const remainingParameterUpdates = parameterUpdates.reduce(
|
|
250
|
+
(acc, parameterUpdate) => {
|
|
251
|
+
acc[parameterUpdate.componentId + parameterUpdate.parameterId] = parameterUpdate;
|
|
252
|
+
return acc;
|
|
253
|
+
},
|
|
254
|
+
{}
|
|
255
|
+
);
|
|
256
|
+
for (const parameterUpdate of parameterUpdates ?? []) {
|
|
257
|
+
walkNodeTree(draft.composition, ({ node, type }) => {
|
|
258
|
+
if (node._id !== parameterUpdate.componentId) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
let properties;
|
|
262
|
+
if (type === "component") {
|
|
263
|
+
node.parameters ??= {};
|
|
264
|
+
properties = node.parameters;
|
|
265
|
+
} else {
|
|
266
|
+
node.fields ??= {};
|
|
267
|
+
properties = node.fields;
|
|
268
|
+
}
|
|
269
|
+
properties[parameterUpdate.parameterId] ??= { type: parameterUpdate.parameterType };
|
|
270
|
+
const property = properties[parameterUpdate.parameterId];
|
|
271
|
+
if (parameterUpdate.locale) {
|
|
272
|
+
property.locales ??= {};
|
|
273
|
+
property.locales[parameterUpdate.locale] = parameterUpdate.value;
|
|
274
|
+
} else {
|
|
275
|
+
property.value = parameterUpdate.value;
|
|
276
|
+
}
|
|
277
|
+
delete remainingParameterUpdates[parameterUpdate.componentId + parameterUpdate.parameterId];
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
if (Object.keys(remainingParameterUpdates).length > 0) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
`Parameter updates with invalid component IDs were not applied: ${Object.keys(remainingParameterUpdates).join(", ")}`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// src/tools/componentPattern/componentPatternList.ts
|
|
289
|
+
import { CANVAS_DRAFT_STATE as CANVAS_DRAFT_STATE2, CanvasClient as CanvasClient2 } from "@uniformdev/canvas";
|
|
290
|
+
import { z as z4 } from "zod";
|
|
291
|
+
var componentInstanceListOptions = {
|
|
292
|
+
instanceType: z4.enum(["componentPattern", "compositionPattern"])
|
|
293
|
+
};
|
|
294
|
+
var componentInstanceGetOptions = {
|
|
295
|
+
id: z4.string()
|
|
296
|
+
};
|
|
297
|
+
function registerComponentPatternList(server2) {
|
|
298
|
+
server2.tool(
|
|
299
|
+
"componentPatternList",
|
|
300
|
+
"Lists available Uniform component patterns or composition patterns",
|
|
301
|
+
componentInstanceListOptions,
|
|
302
|
+
async ({ instanceType }) => {
|
|
303
|
+
const canvasClient = new CanvasClient2(env);
|
|
304
|
+
const components = await canvasClient.getCompositionList({
|
|
305
|
+
patternType: instanceType === "componentPattern" ? "component" : "composition",
|
|
306
|
+
pattern: true,
|
|
307
|
+
resolveData: false,
|
|
308
|
+
withComponentIDs: true,
|
|
309
|
+
skipOverridesResolution: true,
|
|
310
|
+
limit: 100,
|
|
311
|
+
skipPatternResolution: true,
|
|
312
|
+
state: CANVAS_DRAFT_STATE2
|
|
313
|
+
});
|
|
314
|
+
const componentInstances = components.compositions.map(
|
|
315
|
+
(composition) => ({
|
|
316
|
+
id: composition.composition._id,
|
|
317
|
+
name: composition.composition._name,
|
|
318
|
+
componentType: composition.composition.type,
|
|
319
|
+
editUrl: linkToComponentPattern(composition.composition._id)
|
|
320
|
+
})
|
|
321
|
+
);
|
|
322
|
+
return {
|
|
323
|
+
content: [
|
|
324
|
+
{
|
|
325
|
+
type: "text",
|
|
326
|
+
text: JSON.stringify(componentInstances, null, 2)
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
server2.tool(
|
|
333
|
+
"componentPatternGet",
|
|
334
|
+
"Gets the full contents of a Uniform component pattern or composition pattern",
|
|
335
|
+
componentInstanceGetOptions,
|
|
336
|
+
async ({ id }) => {
|
|
337
|
+
const canvasClient = new CanvasClient2(env);
|
|
338
|
+
const pattern = await canvasClient.getCompositionById({
|
|
339
|
+
skipDataResolution: true,
|
|
340
|
+
withComponentIDs: true,
|
|
341
|
+
skipOverridesResolution: true,
|
|
342
|
+
skipPatternResolution: true,
|
|
343
|
+
compositionId: id,
|
|
344
|
+
state: CANVAS_DRAFT_STATE2
|
|
345
|
+
});
|
|
346
|
+
return {
|
|
347
|
+
content: [
|
|
348
|
+
{
|
|
349
|
+
type: "text",
|
|
350
|
+
text: JSON.stringify(pattern, null, 2)
|
|
351
|
+
}
|
|
352
|
+
]
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/tools/components/componentAction.ts
|
|
359
|
+
import { CanvasClient as CanvasClient3 } from "@uniformdev/canvas";
|
|
360
|
+
import { z as z7 } from "zod";
|
|
361
|
+
|
|
362
|
+
// src/patches/applyPatch.ts
|
|
363
|
+
import jsonPatch from "fast-json-patch";
|
|
364
|
+
function applyJsonPatch(document, patch) {
|
|
365
|
+
return jsonPatch.applyPatch(document, patch).newDocument;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/patches/types.ts
|
|
369
|
+
import { z as z5 } from "zod";
|
|
370
|
+
var jsonPatchSchema = z5.array(
|
|
371
|
+
z5.object({
|
|
372
|
+
op: z5.enum(["add", "remove", "replace"]),
|
|
373
|
+
path: z5.string().describe("The JSON path to patch"),
|
|
374
|
+
value: z5.any().describe("The value to patch - for remove, leave this blank")
|
|
375
|
+
})
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
// src/tools/components/util.ts
|
|
379
|
+
import { z as z6 } from "zod";
|
|
380
|
+
var aiComponentDefinitionSchema = z6.object({
|
|
381
|
+
name: z6.string().describe('Title-cased display name of the component. Ex: "Hero"'),
|
|
382
|
+
id: z6.string().describe(
|
|
383
|
+
"Public ID of the component, the camelCase version of name, using only alphanumeric characters"
|
|
384
|
+
),
|
|
385
|
+
description: z6.string().describe(
|
|
386
|
+
'Describe the purpose of the component in 1-2 sentences. If the component seems generic, such as "Promo" or "Hero", this should be blank.'
|
|
387
|
+
),
|
|
388
|
+
canBeComposition: z6.boolean().describe(
|
|
389
|
+
"True if the component can have pages/compositions created from it. False if it is a part of a page."
|
|
390
|
+
),
|
|
391
|
+
parameters: z6.array(propertyDefinitionSchema).describe("The properties of the component. The key is the publicId of the property.").optional(),
|
|
392
|
+
slots: z6.array(
|
|
393
|
+
z6.object({
|
|
394
|
+
name: z6.string().describe('Title-cased display name of the slot. Ex: "Content"'),
|
|
395
|
+
id: z6.string().describe("The public id of the slot. Must be unique within this component's slots."),
|
|
396
|
+
allowedComponents: z6.array(z6.string()).describe("The components public IDs allowed in the slot."),
|
|
397
|
+
minComponents: z6.number().describe("The minimum number of components allowed in the slot. 0 for no minimum.").optional(),
|
|
398
|
+
maxComponents: z6.number().describe("The maximum number of components allowed in the slot. 0 for no maximum.").optional(),
|
|
399
|
+
inheritAllowedComponents: z6.boolean().describe("Set to false")
|
|
400
|
+
})
|
|
401
|
+
).describe("The slots of the component. The key is the publicId of the slot.").optional()
|
|
402
|
+
});
|
|
403
|
+
function applyDefaultsForNewAiCreatedUniformComponents(component) {
|
|
404
|
+
const slots = component.slots?.map((slot) => {
|
|
405
|
+
return {
|
|
406
|
+
...slot,
|
|
407
|
+
patternsInAllowedComponents: true,
|
|
408
|
+
inheritAllowedComponents: false
|
|
409
|
+
};
|
|
410
|
+
});
|
|
411
|
+
const thumbnailParameter = component.parameters?.find(
|
|
412
|
+
(property) => property.type === "asset" && typeof property.typeConfig === "object" && property.typeConfig !== null && "allowedTypes" in property.typeConfig && Array.isArray(property.typeConfig.allowedTypes) && property.typeConfig.allowedTypes?.includes("image")
|
|
413
|
+
)?.id;
|
|
414
|
+
const titleParameter = component.parameters?.find((property) => property.type === "text")?.id;
|
|
415
|
+
return {
|
|
416
|
+
...component,
|
|
417
|
+
slots,
|
|
418
|
+
titleParameter,
|
|
419
|
+
thumbnailParameter
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function linkToComponentDefinition(id) {
|
|
423
|
+
return `${env.baseWebUrl}/dashboards/canvas/components/${id}`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/tools/components/componentAction.ts
|
|
427
|
+
var componentActionSchema = {
|
|
428
|
+
action: z7.enum(["create", "update", "delete"]).describe("The action to perform"),
|
|
429
|
+
publicId: z7.string().describe(
|
|
430
|
+
"Public ID of the component, the camelCase version of name, using only alphanumeric characters"
|
|
431
|
+
),
|
|
432
|
+
component: aiComponentDefinitionSchema.describe("Full component definition to create. Omit for update or delete.").optional(),
|
|
433
|
+
patches: jsonPatchSchema.describe("JSON patch to apply to the existing component definition. Omit for create or delete.").optional()
|
|
434
|
+
};
|
|
435
|
+
function registerComponentAction(server2) {
|
|
436
|
+
server2.tool(
|
|
437
|
+
"componentAction",
|
|
438
|
+
"Creates, updates, or deletes a Uniform Component definition (or Composition definition)",
|
|
439
|
+
componentActionSchema,
|
|
440
|
+
async ({ publicId, action, component, patches }) => {
|
|
441
|
+
const canvasClient = new CanvasClient3(env);
|
|
442
|
+
if (action === "create") {
|
|
443
|
+
if (!component) {
|
|
444
|
+
throw new Error("Component definition is required for create");
|
|
445
|
+
}
|
|
446
|
+
await canvasClient.updateComponentDefinition({
|
|
447
|
+
componentDefinition: applyDefaultsForNewAiCreatedUniformComponents(component)
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (action === "update") {
|
|
451
|
+
if (!patches) {
|
|
452
|
+
throw new Error("Patches are required for update");
|
|
453
|
+
}
|
|
454
|
+
const existingComponent = await canvasClient.getComponentDefinitions({ componentId: publicId });
|
|
455
|
+
if (!existingComponent.componentDefinitions.length) {
|
|
456
|
+
throw new Error(`Component with public ID ${publicId} does not exist, try creating it instead`);
|
|
457
|
+
}
|
|
458
|
+
const existingComponentDefinitionToPatch = existingComponent.componentDefinitions[0];
|
|
459
|
+
const newDocument = applyJsonPatch(existingComponentDefinitionToPatch, patches);
|
|
460
|
+
await canvasClient.updateComponentDefinition({
|
|
461
|
+
componentDefinition: newDocument
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
if (action === "delete") {
|
|
465
|
+
await canvasClient.removeComponentDefinition({ componentId: publicId });
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
content: [
|
|
469
|
+
{
|
|
470
|
+
type: "text",
|
|
471
|
+
text: JSON.stringify({ success: true, editUrl: linkToComponentDefinition(publicId) }, null, 2)
|
|
472
|
+
}
|
|
473
|
+
]
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/tools/components/componentList.ts
|
|
480
|
+
import { CanvasClient as CanvasClient4 } from "@uniformdev/canvas";
|
|
481
|
+
function registerComponentList(server2) {
|
|
482
|
+
server2.tool("componentList", "Lists all Uniform Component definitions", async () => {
|
|
483
|
+
const canvasClient = new CanvasClient4(env);
|
|
484
|
+
const components = await canvasClient.getComponentDefinitions();
|
|
485
|
+
const toolResult = components.componentDefinitions.map((component) => ({
|
|
486
|
+
componentDefinition: component,
|
|
487
|
+
editUrl: linkToComponentDefinition(component.id)
|
|
488
|
+
}));
|
|
489
|
+
return {
|
|
490
|
+
content: [
|
|
491
|
+
{
|
|
492
|
+
type: "text",
|
|
493
|
+
text: JSON.stringify(toolResult, null, 2)
|
|
494
|
+
}
|
|
495
|
+
]
|
|
496
|
+
};
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/tools/contentTypes/contentTypeAction.ts
|
|
501
|
+
import { ContentClient } from "@uniformdev/canvas";
|
|
502
|
+
import { z as z9 } from "zod";
|
|
503
|
+
|
|
504
|
+
// src/tools/contentTypes/util.ts
|
|
505
|
+
import { z as z8 } from "zod";
|
|
506
|
+
var aiContentTypeDefinitionSchema2 = z8.object({
|
|
507
|
+
name: z8.string().describe('Title-cased display name of the component. Ex: "Hero"'),
|
|
508
|
+
id: z8.string().describe(
|
|
509
|
+
"Public ID of the content type, the camelCase version of name, using only alphanumeric characters"
|
|
510
|
+
),
|
|
511
|
+
description: z8.string().describe("Describe the purpose of the content type in 1-2 sentences."),
|
|
512
|
+
fields: z8.array(propertyDefinitionSchema).describe("The fields of the content type. The key is the publicId of the field.").optional()
|
|
513
|
+
});
|
|
514
|
+
function applyDefaultsForNewAiCreatedUniformContentTypes(contentType) {
|
|
515
|
+
const thumbnailField = contentType.fields?.find(
|
|
516
|
+
(property) => property.type === "asset" && typeof property.typeConfig === "object" && property.typeConfig !== null && "allowedTypes" in property.typeConfig && Array.isArray(property.typeConfig.allowedTypes) && property.typeConfig.allowedTypes?.includes("image")
|
|
517
|
+
)?.id;
|
|
518
|
+
const entryName = contentType.fields?.find((field) => field.type === "text")?.id;
|
|
519
|
+
return {
|
|
520
|
+
...contentType,
|
|
521
|
+
thumbnailField,
|
|
522
|
+
entryName
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function linkToContentType(id) {
|
|
526
|
+
return `${env.baseWebUrl}/dashboards/canvas/content-types/${id}`;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/tools/contentTypes/contentTypeAction.ts
|
|
530
|
+
var contentTypeActionSchema = {
|
|
531
|
+
action: z9.enum(["create", "update", "delete"]).describe("The action to perform"),
|
|
532
|
+
publicId: z9.string().describe(
|
|
533
|
+
"Public ID of the component, the camelCase version of name, using only alphanumeric characters"
|
|
534
|
+
),
|
|
535
|
+
contentType: aiContentTypeDefinitionSchema2.describe("Full content type definition to create. Omit for update or delete.").optional(),
|
|
536
|
+
patches: jsonPatchSchema.describe("JSON patch to apply to the existing component definition. Omit for create or delete.").optional()
|
|
537
|
+
};
|
|
538
|
+
function registerContentTypeAction(server2) {
|
|
539
|
+
server2.tool(
|
|
540
|
+
"contentTypeAction",
|
|
541
|
+
"Creates, updates, or deletes a Uniform Content Type definition",
|
|
542
|
+
contentTypeActionSchema,
|
|
543
|
+
async ({ publicId, action, contentType, patches }) => {
|
|
544
|
+
const contentClient = new ContentClient(env);
|
|
545
|
+
if (action === "create") {
|
|
546
|
+
if (!contentType) {
|
|
547
|
+
throw new Error("Content type definition is required for create");
|
|
548
|
+
}
|
|
549
|
+
await contentClient.upsertContentType({
|
|
550
|
+
contentType: applyDefaultsForNewAiCreatedUniformContentTypes(contentType)
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
if (action === "update") {
|
|
554
|
+
if (!patches) {
|
|
555
|
+
throw new Error("Patches are required for update");
|
|
556
|
+
}
|
|
557
|
+
const existingContentTypes = await contentClient.getContentTypes();
|
|
558
|
+
const existingContentType = existingContentTypes.contentTypes.find(
|
|
559
|
+
(contentType2) => contentType2.id === publicId
|
|
560
|
+
);
|
|
561
|
+
if (!existingContentType) {
|
|
562
|
+
throw new Error(`Content Type with public ID ${publicId} does not exist, try creating it instead`);
|
|
563
|
+
}
|
|
564
|
+
const newDocument = applyJsonPatch(existingContentType, patches);
|
|
565
|
+
await contentClient.upsertContentType({
|
|
566
|
+
contentType: newDocument
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
if (action === "delete") {
|
|
570
|
+
await contentClient.deleteContentType({ contentTypeId: publicId });
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
content: [
|
|
574
|
+
{
|
|
575
|
+
type: "text",
|
|
576
|
+
text: JSON.stringify({ success: true, editUrl: linkToContentType(publicId) }, null, 2)
|
|
577
|
+
}
|
|
578
|
+
]
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// src/tools/contentTypes/contentTypeList.ts
|
|
585
|
+
import { ContentClient as ContentClient2 } from "@uniformdev/canvas";
|
|
586
|
+
function registerContentTypeList(server2) {
|
|
587
|
+
server2.tool("contentTypeList", "Lists all Uniform Content Type definitions", async () => {
|
|
588
|
+
const contentClient = new ContentClient2(env);
|
|
589
|
+
const components = await contentClient.getContentTypes();
|
|
590
|
+
const toolResult = components.contentTypes.map((contentType) => ({
|
|
591
|
+
contentType,
|
|
592
|
+
editUrl: linkToContentType(contentType.id)
|
|
593
|
+
}));
|
|
594
|
+
return {
|
|
595
|
+
content: [
|
|
596
|
+
{
|
|
597
|
+
type: "text",
|
|
598
|
+
text: JSON.stringify(toolResult, null, 2)
|
|
599
|
+
}
|
|
600
|
+
]
|
|
601
|
+
};
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// src/index.ts
|
|
606
|
+
var server = new McpServer({
|
|
607
|
+
name: "Uniform MCP",
|
|
608
|
+
version: "1.0.0"
|
|
609
|
+
});
|
|
610
|
+
registerComponentAction(server);
|
|
611
|
+
registerComponentList(server);
|
|
612
|
+
registerContentTypeAction(server);
|
|
613
|
+
registerContentTypeList(server);
|
|
614
|
+
registerComponentPatternList(server);
|
|
615
|
+
registerComponentPatternAction(server);
|
|
616
|
+
var transport = new StdioServerTransport();
|
|
617
|
+
await server.connect(transport);
|
|
618
|
+
process.stderr.write("Uniform MCP server started\n");
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uniformdev/uniform-mcp",
|
|
3
|
+
"version": "20.14.2-alpha.35+298a00bb0a",
|
|
4
|
+
"description": "Uniform MCP Server",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.esm.js",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"uniform-mcp": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
"types": "./dist/index.d.mts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"clean": "rimraf dist",
|
|
22
|
+
"test": "vitest --passWithNoTests",
|
|
23
|
+
"lint": "eslint \"src/**/*.{js,ts,tsx}\"",
|
|
24
|
+
"format": "prettier --write \"src/**/*.{js,ts,tsx}\"",
|
|
25
|
+
"mcp": "node dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
29
|
+
"@uniformdev/canvas": "20.14.2-alpha.35+298a00bb0a",
|
|
30
|
+
"fast-json-patch": "^3.1.1",
|
|
31
|
+
"immer": "10.1.1",
|
|
32
|
+
"zod": "3.23.8"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"/dist"
|
|
36
|
+
],
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"vitest": "^3.1.4"
|
|
42
|
+
},
|
|
43
|
+
"gitHead": "298a00bb0a2955cf07d94dd6ef939a64b5bfdcd0"
|
|
44
|
+
}
|