uiplug-mcp 1.3.3 → 1.3.5

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 (3) hide show
  1. package/README.md +13 -2
  2. package/dist/index.js +212 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,13 +4,18 @@ MCP server for [UIPlug](https://uiplug.com) — gives Claude and other AI agents
4
4
 
5
5
  ## What it does
6
6
 
7
- Exposes three tools to any MCP-compatible agent:
7
+ Exposes 8 tools to any MCP-compatible agent:
8
8
 
9
9
  | Tool | Description |
10
10
  |------|-------------|
11
11
  | `list_components` | Browse published components, filter by framework / category |
12
- | `search_components` | Search by name, description, or tag |
12
+ | `search_components` | Search public marketplace by name, description, or tag |
13
13
  | `get_component` | Get full source code + installation instructions |
14
+ | `get_my_profile` | View your UIPlug profile and component stats |
15
+ | `list_my_components` | List all components you've created (including pending) |
16
+ | `search_my_components` | Search through your own saved components |
17
+ | `list_groups` | List UIPlug groups you're a member of |
18
+ | `create_component` | Submit a new component to the marketplace |
14
19
 
15
20
  ## Authentication
16
21
 
@@ -44,6 +49,12 @@ Restart Claude Desktop.
44
49
 
45
50
  > "Get the Gradient Button component from UIPlug and adapt it to my design system."
46
51
 
52
+ > "Show me all my UIPlug components that are still pending review."
53
+
54
+ > "Search my saved UIPlug components for anything button-related."
55
+
56
+ > "What's my UIPlug profile and how many downloads do I have?"
57
+
47
58
  ## Supported frameworks
48
59
 
49
60
  React · Vue · Svelte · Angular · HTML/CSS · Jetpack Compose · Compose Multiplatform · Flutter · SwiftUI · React Native
package/dist/index.js CHANGED
@@ -109,6 +109,68 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
109
109
  },
110
110
  },
111
111
  },
112
+ {
113
+ name: "get_my_profile",
114
+ description: "Get your UIPlug profile information including username, bio, avatar, " +
115
+ "and component stats. Requires a valid UIPLUG_API_KEY.",
116
+ inputSchema: {
117
+ type: "object",
118
+ properties: {},
119
+ },
120
+ },
121
+ {
122
+ name: "list_my_components",
123
+ description: "List all UI components you have created on UIPlug, including pending and published ones. " +
124
+ "Requires a valid UIPLUG_API_KEY. Use this to see your own saved components, check their status, " +
125
+ "or find IDs to pass to get_component.",
126
+ inputSchema: {
127
+ type: "object",
128
+ properties: {
129
+ framework: {
130
+ type: "string",
131
+ description: "Filter by framework (optional).",
132
+ },
133
+ category: {
134
+ type: "string",
135
+ description: "Filter by category (optional).",
136
+ },
137
+ status: {
138
+ type: "string",
139
+ enum: ["pending", "published", "rejected"],
140
+ description: "Filter by status (optional). Omit to return all.",
141
+ },
142
+ limit: {
143
+ type: "number",
144
+ description: "Maximum number of results to return (default 20, max 50).",
145
+ },
146
+ },
147
+ },
148
+ },
149
+ {
150
+ name: "search_my_components",
151
+ description: "Search through your own UIPlug components by name, description, or tag. " +
152
+ "Unlike search_components (which searches the public marketplace), this only searches " +
153
+ "components you have created — including pending or unpublished ones.",
154
+ inputSchema: {
155
+ type: "object",
156
+ required: ["query"],
157
+ properties: {
158
+ query: {
159
+ type: "string",
160
+ description: "Search term — matched against name, description, and tags.",
161
+ },
162
+ framework: {
163
+ type: "string",
164
+ description: "Narrow results to a specific framework (optional).",
165
+ },
166
+ status: {
167
+ type: "string",
168
+ enum: ["pending", "published", "rejected"],
169
+ description: "Filter by status (optional). Omit to return all.",
170
+ },
171
+ },
172
+ },
173
+ },
112
174
  {
113
175
  name: "list_groups",
114
176
  description: "List the UIPlug groups you are an active member of. " +
@@ -431,6 +493,156 @@ ${c.code_component}
431
493
  `;
432
494
  return { content: [{ type: "text", text: output }] };
433
495
  }
496
+ // ── get_my_profile ──────────────────────────────────────────────────────────
497
+ if (name === "get_my_profile") {
498
+ const [profileRes, statsRes] = await Promise.all([
499
+ supabase
500
+ .from("profiles")
501
+ .select("username, full_name, bio, avatar_url, website, created_at")
502
+ .eq("id", userId)
503
+ .single(),
504
+ supabase
505
+ .from("components")
506
+ .select("id, status, downloads, likes")
507
+ .eq("author_id", userId),
508
+ ]);
509
+ if (profileRes.error || !profileRes.data) {
510
+ return { content: [{ type: "text", text: `Profile not found: ${profileRes.error?.message}` }], isError: true };
511
+ }
512
+ const p = profileRes.data;
513
+ const components = statsRes.data ?? [];
514
+ const published = components.filter((c) => c.status === "published").length;
515
+ const pending = components.filter((c) => c.status === "pending").length;
516
+ const totalDownloads = components.reduce((sum, c) => sum + (c.downloads ?? 0), 0);
517
+ const totalLikes = components.reduce((sum, c) => sum + (c.likes ?? 0), 0);
518
+ return {
519
+ content: [{
520
+ type: "text",
521
+ text: `# Your UIPlug Profile\n\n` +
522
+ `**Username:** ${p.username ?? "—"}\n` +
523
+ `**Full Name:** ${p.full_name ?? "—"}\n` +
524
+ `**Bio:** ${p.bio ?? "—"}\n` +
525
+ `**Website:** ${p.website ?? "—"}\n` +
526
+ `**Member since:** ${p.created_at ? new Date(p.created_at).toDateString() : "—"}\n\n` +
527
+ `## Component Stats\n` +
528
+ `- Total components: ${components.length}\n` +
529
+ `- Published: ${published}\n` +
530
+ `- Pending review: ${pending}\n` +
531
+ `- Total downloads: ${totalDownloads}\n` +
532
+ `- Total likes: ${totalLikes}`,
533
+ }],
534
+ };
535
+ }
536
+ // ── list_my_components ──────────────────────────────────────────────────────
537
+ if (name === "list_my_components") {
538
+ const { framework, category, status, limit = 20 } = (args ?? {});
539
+ let query = supabase
540
+ .from("components")
541
+ .select("id, name, description, category, framework, downloads, likes, model, status, visibility, created_at")
542
+ .eq("author_id", userId)
543
+ .order("created_at", { ascending: false })
544
+ .limit(Math.min(limit, 50));
545
+ if (framework)
546
+ query = query.eq("framework", framework);
547
+ if (category)
548
+ query = query.eq("category", category);
549
+ if (status)
550
+ query = query.eq("status", status);
551
+ const { data, error } = await query;
552
+ if (error) {
553
+ return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
554
+ }
555
+ const rows = data ?? [];
556
+ if (rows.length === 0) {
557
+ return { content: [{ type: "text", text: "You have no saved components yet. Use create_component to add one." }] };
558
+ }
559
+ return {
560
+ content: [{
561
+ type: "text",
562
+ text: `You have ${rows.length} component(s):\n\n` +
563
+ rows.map((c) => {
564
+ const visTag = c.visibility === "private" ? " 🔒" : "";
565
+ const statusEmoji = c.status === "published" ? "✅" : c.status === "pending" ? "⏳" : "❌";
566
+ return (`${statusEmoji} **${c.name}**${visTag} (${c.framework} · ${c.category})\n` +
567
+ ` ID: ${c.id}\n` +
568
+ ` ${c.description}\n` +
569
+ ` Status: ${c.status} · ↓${c.downloads ?? 0} ♥${c.likes ?? 0}` +
570
+ (c.model ? ` · Built with ${c.model}` : ""));
571
+ }).join("\n\n"),
572
+ }],
573
+ };
574
+ }
575
+ // ── search_my_components ────────────────────────────────────────────────────
576
+ if (name === "search_my_components") {
577
+ const { query: q, framework, status } = (args ?? {});
578
+ // Search name + description
579
+ let nameQuery = supabase
580
+ .from("components")
581
+ .select("id, name, description, category, framework, downloads, likes, status, visibility, model, created_at")
582
+ .eq("author_id", userId)
583
+ .or(`name.ilike.%${q}%,description.ilike.%${q}%`)
584
+ .order("created_at", { ascending: false })
585
+ .limit(30);
586
+ if (framework)
587
+ nameQuery = nameQuery.eq("framework", framework);
588
+ if (status)
589
+ nameQuery = nameQuery.eq("status", status);
590
+ // Also search by tag
591
+ const { data: tagData } = await supabase
592
+ .from("tags")
593
+ .select("id")
594
+ .ilike("name", `%${q}%`);
595
+ const tagIds = (tagData ?? []).map((t) => t.id);
596
+ let tagComponentIds = [];
597
+ if (tagIds.length > 0) {
598
+ const { data: ctData } = await supabase
599
+ .from("component_tags")
600
+ .select("component_id")
601
+ .in("tag_id", tagIds);
602
+ tagComponentIds = (ctData ?? []).map((ct) => ct.component_id);
603
+ }
604
+ const { data: nameResults, error } = await nameQuery;
605
+ if (error) {
606
+ return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
607
+ }
608
+ // Fetch tag-matched components owned by the user not already in name results
609
+ let tagResults = [];
610
+ const existingIds = new Set((nameResults ?? []).map((c) => c.id));
611
+ const newTagIds = tagComponentIds.filter((id) => !existingIds.has(id));
612
+ if (newTagIds.length > 0) {
613
+ let tq = supabase
614
+ .from("components")
615
+ .select("id, name, description, category, framework, downloads, likes, status, visibility, model, created_at")
616
+ .eq("author_id", userId)
617
+ .in("id", newTagIds)
618
+ .limit(10);
619
+ if (framework)
620
+ tq = tq.eq("framework", framework);
621
+ if (status)
622
+ tq = tq.eq("status", status);
623
+ const { data } = await tq;
624
+ tagResults = data ?? [];
625
+ }
626
+ const all = [...(nameResults ?? []), ...tagResults];
627
+ if (all.length === 0) {
628
+ return { content: [{ type: "text", text: `No components found matching "${q}" in your saved components.` }] };
629
+ }
630
+ return {
631
+ content: [{
632
+ type: "text",
633
+ text: `Found ${all.length} of your component(s) matching "${q}":\n\n` +
634
+ all.map((c) => {
635
+ const visTag = c.visibility === "private" ? " 🔒" : "";
636
+ const statusEmoji = c.status === "published" ? "✅" : c.status === "pending" ? "⏳" : "❌";
637
+ return (`${statusEmoji} **${c.name}**${visTag} (${c.framework} · ${c.category})\n` +
638
+ ` ID: ${c.id}\n` +
639
+ ` ${c.description}\n` +
640
+ ` Status: ${c.status} · ↓${c.downloads ?? 0} ♥${c.likes ?? 0}` +
641
+ (c.model ? ` · Built with ${c.model}` : ""));
642
+ }).join("\n\n"),
643
+ }],
644
+ };
645
+ }
434
646
  // ── list_groups ─────────────────────────────────────────────────────────────
435
647
  if (name === "list_groups") {
436
648
  const { createHash: ch } = await import("crypto");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uiplug-mcp",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "MCP server for UIPlug — gives AI agents access to the UIPlug UI component marketplace",
5
5
  "type": "module",
6
6
  "bin": {