uiplug-mcp 1.2.2 → 1.3.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/dist/index.js +169 -38
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -40,8 +40,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
40
40
|
{
|
|
41
41
|
name: "list_components",
|
|
42
42
|
description: "List published UI components from the UIPlug marketplace. " +
|
|
43
|
-
"Optionally filter by framework
|
|
44
|
-
"
|
|
43
|
+
"Optionally filter by framework, category, or group_slug. " +
|
|
44
|
+
"When group_slug is set, returns all components in that group (including private ones if you are a member). " +
|
|
45
|
+
"Use list_groups to find your group slugs.",
|
|
45
46
|
inputSchema: {
|
|
46
47
|
type: "object",
|
|
47
48
|
properties: {
|
|
@@ -58,13 +59,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
58
59
|
type: "number",
|
|
59
60
|
description: "Maximum number of results to return (default 20, max 50).",
|
|
60
61
|
},
|
|
62
|
+
group_slug: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: "Filter to a specific group (e.g. 'acme-ui-team'). " +
|
|
65
|
+
"Members of the group will also see private components. " +
|
|
66
|
+
"Use list_groups to find your group slugs.",
|
|
67
|
+
},
|
|
61
68
|
},
|
|
62
69
|
},
|
|
63
70
|
},
|
|
64
71
|
{
|
|
65
72
|
name: "search_components",
|
|
66
73
|
description: "Search UIPlug components by name, description, or tag. " +
|
|
67
|
-
"Returns matching components with a summary of their metadata."
|
|
74
|
+
"Returns matching components with a summary of their metadata. " +
|
|
75
|
+
"To search within a specific group (including private components), provide group_slug.",
|
|
68
76
|
inputSchema: {
|
|
69
77
|
type: "object",
|
|
70
78
|
required: ["query"],
|
|
@@ -77,6 +85,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
77
85
|
type: "string",
|
|
78
86
|
description: "Narrow results to a specific framework.",
|
|
79
87
|
},
|
|
88
|
+
group_slug: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: "Narrow results to a specific group. " +
|
|
91
|
+
"Members of the group will also see private components.",
|
|
92
|
+
},
|
|
80
93
|
},
|
|
81
94
|
},
|
|
82
95
|
},
|
|
@@ -96,11 +109,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
96
109
|
},
|
|
97
110
|
},
|
|
98
111
|
},
|
|
112
|
+
{
|
|
113
|
+
name: "list_groups",
|
|
114
|
+
description: "List the UIPlug groups you are an active member of. " +
|
|
115
|
+
"Returns each group's name and slug. Use the slug in create_component to submit a component on behalf of a group.",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
99
121
|
{
|
|
100
122
|
name: "create_component",
|
|
101
123
|
description: "Submit a new UI component to the UIPlug marketplace. " +
|
|
102
124
|
"The component will be submitted for review (status: pending) and visible in your dashboard at uiplug.com/dashboard/components. " +
|
|
103
125
|
"Use this after building a component to share it with the community. " +
|
|
126
|
+
"To submit on behalf of a group, provide group_slug (use list_groups to find it). " +
|
|
127
|
+
"Group components can be public (visible to everyone) or private (visible only to group members). " +
|
|
104
128
|
"IMPORTANT — code quality standard: the code field must be a complete, self-contained file. " +
|
|
105
129
|
"It must start with all necessary imports (e.g. import { useState } from 'react'), " +
|
|
106
130
|
"contain the full component implementation (not just a snippet or function body), " +
|
|
@@ -151,6 +175,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
151
175
|
type: "string",
|
|
152
176
|
description: "Optional — AI model used to build this (e.g. 'Claude Sonnet 4.6').",
|
|
153
177
|
},
|
|
178
|
+
group_slug: {
|
|
179
|
+
type: "string",
|
|
180
|
+
description: "Optional — submit this component on behalf of a group. " +
|
|
181
|
+
"Use the slug from list_groups (e.g. 'acme-ui-team'). " +
|
|
182
|
+
"The component will go to the group admin for review. " +
|
|
183
|
+
"You must be an active member of the group.",
|
|
184
|
+
},
|
|
185
|
+
visibility: {
|
|
186
|
+
type: "string",
|
|
187
|
+
enum: ["public", "private"],
|
|
188
|
+
description: "Only relevant when group_slug is set. " +
|
|
189
|
+
"'public' = visible to everyone in Explore (default). " +
|
|
190
|
+
"'private' = visible only to group members. " +
|
|
191
|
+
"Personal components (no group_slug) are always public.",
|
|
192
|
+
},
|
|
154
193
|
},
|
|
155
194
|
},
|
|
156
195
|
},
|
|
@@ -166,44 +205,64 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
166
205
|
}
|
|
167
206
|
// ── list_components ─────────────────────────────────────────────────────────
|
|
168
207
|
if (name === "list_components") {
|
|
169
|
-
const { framework, category, limit = 20 } = (args ?? {});
|
|
170
|
-
let
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
208
|
+
const { framework, category, limit = 20, group_slug } = (args ?? {});
|
|
209
|
+
let rows = [];
|
|
210
|
+
if (group_slug) {
|
|
211
|
+
// Use SECURITY DEFINER RPC so private group components are visible to members
|
|
212
|
+
const { createHash: ch } = await import("crypto");
|
|
213
|
+
const kh = ch("sha256").update(process.env.UIPLUG_API_KEY ?? "").digest("hex");
|
|
214
|
+
const { data, error } = await supabase.rpc("get_group_components", {
|
|
215
|
+
p_key_hash: kh,
|
|
216
|
+
p_group_slug: group_slug,
|
|
217
|
+
p_search: null,
|
|
218
|
+
p_limit: Math.min(limit, 50),
|
|
219
|
+
});
|
|
220
|
+
if (error) {
|
|
221
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
222
|
+
}
|
|
223
|
+
rows = (data ?? []).filter((c) => (!framework || c.framework === framework) &&
|
|
224
|
+
(!category || c.category === category));
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
let query = supabase
|
|
228
|
+
.from("components")
|
|
229
|
+
.select("id, name, description, category, framework, downloads, likes, model, status, visibility, " +
|
|
230
|
+
"profiles!components_author_id_fkey(username)")
|
|
231
|
+
.eq("status", "published")
|
|
232
|
+
.eq("visibility", "public")
|
|
233
|
+
.order("downloads", { ascending: false })
|
|
234
|
+
.limit(Math.min(limit, 50));
|
|
235
|
+
if (framework)
|
|
236
|
+
query = query.eq("framework", framework);
|
|
237
|
+
if (category)
|
|
238
|
+
query = query.eq("category", category);
|
|
239
|
+
const { data, error } = await query;
|
|
240
|
+
if (error) {
|
|
241
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
242
|
+
}
|
|
243
|
+
rows = (data ?? []).map((c) => ({ ...c, author: c.profiles?.username ?? "Unknown" }));
|
|
244
|
+
}
|
|
245
|
+
if (rows.length === 0) {
|
|
246
|
+
return {
|
|
247
|
+
content: [{ type: "text", text: group_slug ? `No components found in group "${group_slug}".` : "No components found." }],
|
|
248
|
+
};
|
|
184
249
|
}
|
|
185
|
-
const rows = (data ?? []).map((c) => ({
|
|
186
|
-
id: c.id,
|
|
187
|
-
name: c.name,
|
|
188
|
-
description: c.description,
|
|
189
|
-
category: c.category,
|
|
190
|
-
framework: c.framework,
|
|
191
|
-
author: c.profiles?.username ?? "Unknown",
|
|
192
|
-
downloads: c.downloads ?? 0,
|
|
193
|
-
likes: c.likes ?? 0,
|
|
194
|
-
model: c.model ?? null,
|
|
195
|
-
}));
|
|
196
250
|
return {
|
|
197
251
|
content: [
|
|
198
252
|
{
|
|
199
253
|
type: "text",
|
|
200
|
-
text: `Found ${rows.length} component(s).\n\n` +
|
|
254
|
+
text: `Found ${rows.length} component(s)${group_slug ? ` in group "${group_slug}"` : ""}.\n\n` +
|
|
201
255
|
rows
|
|
202
|
-
.map((c) =>
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
(c.
|
|
256
|
+
.map((c) => {
|
|
257
|
+
const author = c.author ?? c.author_username ?? "Unknown";
|
|
258
|
+
const visTag = c.visibility === "private" ? " 🔒" : "";
|
|
259
|
+
const statusTag = c.status !== "published" ? ` [${c.status}]` : "";
|
|
260
|
+
return (`**${c.name}**${visTag}${statusTag} (${c.framework} · ${c.category})\n` +
|
|
261
|
+
` ID: ${c.id}\n` +
|
|
262
|
+
` ${c.description}\n` +
|
|
263
|
+
` Author: ${author} · ↓${c.downloads ?? 0} ♥${c.likes ?? 0}` +
|
|
264
|
+
(c.model ? ` · Built with ${c.model}` : ""));
|
|
265
|
+
})
|
|
207
266
|
.join("\n\n"),
|
|
208
267
|
},
|
|
209
268
|
],
|
|
@@ -211,13 +270,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
211
270
|
}
|
|
212
271
|
// ── search_components ───────────────────────────────────────────────────────
|
|
213
272
|
if (name === "search_components") {
|
|
214
|
-
const { query: q, framework } = (args ?? {});
|
|
273
|
+
const { query: q, framework, group_slug } = (args ?? {});
|
|
274
|
+
// Group-scoped search (includes private components for members)
|
|
275
|
+
if (group_slug) {
|
|
276
|
+
const { createHash: ch } = await import("crypto");
|
|
277
|
+
const kh = ch("sha256").update(process.env.UIPLUG_API_KEY ?? "").digest("hex");
|
|
278
|
+
const { data, error } = await supabase.rpc("get_group_components", {
|
|
279
|
+
p_key_hash: kh,
|
|
280
|
+
p_group_slug: group_slug,
|
|
281
|
+
p_search: q,
|
|
282
|
+
p_limit: 30,
|
|
283
|
+
});
|
|
284
|
+
if (error) {
|
|
285
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
286
|
+
}
|
|
287
|
+
const results = (data ?? []).filter((c) => !framework || c.framework === framework);
|
|
288
|
+
if (results.length === 0) {
|
|
289
|
+
return { content: [{ type: "text", text: `No components found in group "${group_slug}" matching "${q}".` }] };
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
content: [{
|
|
293
|
+
type: "text",
|
|
294
|
+
text: `Found ${results.length} result(s) in group "${group_slug}" for "${q}":\n\n` +
|
|
295
|
+
results.map((c) => {
|
|
296
|
+
const visTag = c.visibility === "private" ? " 🔒" : "";
|
|
297
|
+
const statusTag = c.status !== "published" ? ` [${c.status}]` : "";
|
|
298
|
+
return (`**${c.name}**${visTag}${statusTag} (${c.framework} · ${c.category})\n` +
|
|
299
|
+
` ID: ${c.id}\n ${c.description}\n Author: ${c.author_username ?? "Unknown"}`);
|
|
300
|
+
}).join("\n\n"),
|
|
301
|
+
}],
|
|
302
|
+
};
|
|
303
|
+
}
|
|
215
304
|
// Search name + description via ilike, then also fetch tag matches
|
|
216
305
|
let nameQuery = supabase
|
|
217
306
|
.from("components")
|
|
218
307
|
.select("id, name, description, category, framework, downloads, likes, " +
|
|
219
308
|
"profiles!components_author_id_fkey(username)")
|
|
220
309
|
.eq("status", "published")
|
|
310
|
+
.eq("visibility", "public")
|
|
221
311
|
.or(`name.ilike.%${q}%,description.ilike.%${q}%`)
|
|
222
312
|
.order("downloads", { ascending: false })
|
|
223
313
|
.limit(20);
|
|
@@ -251,6 +341,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
251
341
|
.select("id, name, description, category, framework, downloads, likes, " +
|
|
252
342
|
"profiles!components_author_id_fkey(username)")
|
|
253
343
|
.eq("status", "published")
|
|
344
|
+
.eq("visibility", "public")
|
|
254
345
|
.in("id", newTagIds)
|
|
255
346
|
.limit(10);
|
|
256
347
|
if (framework)
|
|
@@ -340,9 +431,42 @@ ${c.code_component}
|
|
|
340
431
|
`;
|
|
341
432
|
return { content: [{ type: "text", text: output }] };
|
|
342
433
|
}
|
|
434
|
+
// ── list_groups ─────────────────────────────────────────────────────────────
|
|
435
|
+
if (name === "list_groups") {
|
|
436
|
+
const { createHash: ch } = await import("crypto");
|
|
437
|
+
const kh = ch("sha256").update(process.env.UIPLUG_API_KEY ?? "").digest("hex");
|
|
438
|
+
const { data, error } = await supabase.rpc("list_user_groups", { p_key_hash: kh });
|
|
439
|
+
if (error) {
|
|
440
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
441
|
+
}
|
|
442
|
+
const groups = (data ?? []);
|
|
443
|
+
if (groups.length === 0) {
|
|
444
|
+
return {
|
|
445
|
+
content: [
|
|
446
|
+
{
|
|
447
|
+
type: "text",
|
|
448
|
+
text: "You are not a member of any groups yet.\n\n" +
|
|
449
|
+
"Create or join a group at: https://uiplug.com/dashboard/groups",
|
|
450
|
+
},
|
|
451
|
+
],
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
return {
|
|
455
|
+
content: [
|
|
456
|
+
{
|
|
457
|
+
type: "text",
|
|
458
|
+
text: `You are a member of ${groups.length} group(s):\n\n` +
|
|
459
|
+
groups
|
|
460
|
+
.map((g) => `**${g.group_name}** (slug: \`${g.group_slug}\`) — ${g.role}`)
|
|
461
|
+
.join("\n") +
|
|
462
|
+
`\n\nUse the slug in create_component to submit components on behalf of a group.`,
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
};
|
|
466
|
+
}
|
|
343
467
|
// ── create_component ────────────────────────────────────────────────────────
|
|
344
468
|
if (name === "create_component") {
|
|
345
|
-
const { name: compName, description, framework, category, code, installation, tags, model, } = (args ?? {});
|
|
469
|
+
const { name: compName, description, framework, category, code, installation, tags, model, group_slug, visibility = "public", } = (args ?? {});
|
|
346
470
|
const { createHash: createHash2 } = await import("crypto");
|
|
347
471
|
const keyHashForCreate = createHash2("sha256").update(process.env.UIPLUG_API_KEY ?? "").digest("hex");
|
|
348
472
|
const { data: componentId, error } = await supabase.rpc("create_component", {
|
|
@@ -355,10 +479,16 @@ ${c.code_component}
|
|
|
355
479
|
p_installation: installation ?? null,
|
|
356
480
|
p_tags: tags ?? [],
|
|
357
481
|
p_model: model ?? null,
|
|
482
|
+
p_group_slug: group_slug ?? null,
|
|
483
|
+
p_visibility: group_slug ? (visibility ?? "public") : "public",
|
|
358
484
|
});
|
|
359
485
|
if (error) {
|
|
360
486
|
return { content: [{ type: "text", text: `Error submitting component: ${error.message}` }], isError: true };
|
|
361
487
|
}
|
|
488
|
+
const groupNote = group_slug
|
|
489
|
+
? `\n**Group:** ${group_slug} (${visibility === "private" ? "🔒 Private — visible to group members only" : "🌐 Public — visible to everyone"})\n` +
|
|
490
|
+
`The group admin will review it at: https://uiplug.com/dashboard/groups`
|
|
491
|
+
: "";
|
|
362
492
|
return {
|
|
363
493
|
content: [
|
|
364
494
|
{
|
|
@@ -367,7 +497,8 @@ ${c.code_component}
|
|
|
367
497
|
`**Name:** ${compName}\n` +
|
|
368
498
|
`**Framework:** ${framework}\n` +
|
|
369
499
|
`**Category:** ${category}\n` +
|
|
370
|
-
`**ID:** ${componentId}\n
|
|
500
|
+
`**ID:** ${componentId}\n` +
|
|
501
|
+
groupNote + `\n` +
|
|
371
502
|
`View and manage it at: https://uiplug.com/dashboard/components`,
|
|
372
503
|
},
|
|
373
504
|
],
|