@vibesharingapp/mcp-server 0.4.0 → 0.4.1
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 +546 -482
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,6 +4,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
5
5
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
6
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const os_1 = require("os");
|
|
7
10
|
// VibeSharing API client
|
|
8
11
|
class VibesharingClient {
|
|
9
12
|
baseUrl;
|
|
@@ -174,6 +177,59 @@ function fuzzyMatch(query, items, getName, threshold = 0.3) {
|
|
|
174
177
|
.filter((m) => m.score >= threshold)
|
|
175
178
|
.sort((a, b) => b.score - a.score);
|
|
176
179
|
}
|
|
180
|
+
// ---- Version tracking & What's New ----
|
|
181
|
+
const CURRENT_VERSION = "0.4.1";
|
|
182
|
+
const WHATS_NEW = {
|
|
183
|
+
"0.4.0": [
|
|
184
|
+
"🆕 VibeSharing MCP v0.4.0 — What's New:",
|
|
185
|
+
"",
|
|
186
|
+
"• resolve_target tool — Call this before deploying to confirm the collection,",
|
|
187
|
+
" project name, and deploy URL with the user. Fuzzy-matches names so you don't",
|
|
188
|
+
" need exact IDs.",
|
|
189
|
+
"• Named deployments — Use deploy_name on import_repo or deploy_files to set a",
|
|
190
|
+
" friendly Vercel URL (e.g., 'erg-v3-teams' → erg-v3-teams.vercel.app).",
|
|
191
|
+
"• Fuzzy search — list_collections and list_prototypes now accept a 'search'",
|
|
192
|
+
" parameter to filter results.",
|
|
193
|
+
"• Guardrails — Deploy tools now ask for confirmation when collection or project",
|
|
194
|
+
" info is missing, instead of auto-generating everything silently.",
|
|
195
|
+
].join("\n"),
|
|
196
|
+
};
|
|
197
|
+
function getWhatsNew() {
|
|
198
|
+
try {
|
|
199
|
+
const configDir = (0, path_1.join)((0, os_1.homedir)(), ".vibesharing");
|
|
200
|
+
const versionFile = (0, path_1.join)(configDir, "mcp-version");
|
|
201
|
+
let lastVersion = null;
|
|
202
|
+
try {
|
|
203
|
+
lastVersion = (0, fs_1.readFileSync)(versionFile, "utf-8").trim();
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// First run or file missing
|
|
207
|
+
}
|
|
208
|
+
// Update stored version
|
|
209
|
+
try {
|
|
210
|
+
(0, fs_1.mkdirSync)(configDir, { recursive: true });
|
|
211
|
+
(0, fs_1.writeFileSync)(versionFile, CURRENT_VERSION);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// Non-fatal
|
|
215
|
+
}
|
|
216
|
+
if (lastVersion === CURRENT_VERSION)
|
|
217
|
+
return null;
|
|
218
|
+
// Collect all changelogs newer than lastVersion
|
|
219
|
+
const notes = [];
|
|
220
|
+
for (const [ver, note] of Object.entries(WHATS_NEW)) {
|
|
221
|
+
if (!lastVersion || ver > lastVersion) {
|
|
222
|
+
notes.push(note);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return notes.length > 0 ? notes.join("\n\n") : null;
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Check once at startup, prepend to first tool call
|
|
232
|
+
let pendingWhatsNew = getWhatsNew();
|
|
177
233
|
// Get configuration from environment
|
|
178
234
|
const VIBESHARING_URL = process.env.VIBESHARING_URL || "https://vibesharing.app";
|
|
179
235
|
const VIBESHARING_TOKEN = process.env.VIBESHARING_TOKEN;
|
|
@@ -533,612 +589,620 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
533
589
|
// Handle tool calls
|
|
534
590
|
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
535
591
|
const { name, arguments: args } = request.params;
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
592
|
+
const toolResult = await (async () => {
|
|
593
|
+
try {
|
|
594
|
+
switch (name) {
|
|
595
|
+
case "register_prototype": {
|
|
596
|
+
const params = args;
|
|
597
|
+
// Guardrail: if no collection specified, bounce back with options
|
|
598
|
+
if (!params.collection_id) {
|
|
599
|
+
const [guardCollResult, guardProtoResult] = await Promise.all([
|
|
600
|
+
client.listCollections(),
|
|
601
|
+
client.listPrototypes(),
|
|
602
|
+
]);
|
|
603
|
+
const guardCollections = guardCollResult.collections || [];
|
|
604
|
+
const guardPrototypes = guardProtoResult.prototypes || [];
|
|
605
|
+
const similarProjects = fuzzyMatch(params.name, guardPrototypes, (p) => p.name).slice(0, 5);
|
|
606
|
+
const sections = [
|
|
607
|
+
`HOLD ON — confirm with the user before registering "${params.name}":\n`,
|
|
608
|
+
];
|
|
609
|
+
if (similarProjects.length > 0) {
|
|
610
|
+
sections.push(`Similar existing projects:\n` +
|
|
611
|
+
similarProjects.map((m) => {
|
|
612
|
+
const p = m.item;
|
|
613
|
+
return ` - "${p.name}" (ID: ${p.id})`;
|
|
614
|
+
}).join("\n") +
|
|
615
|
+
`\n\nAsk the user: Is this an update to one of these, or a new project?`);
|
|
616
|
+
}
|
|
617
|
+
if (guardCollections.length > 0) {
|
|
618
|
+
sections.push(`\nNo collection specified. Available collections:\n` +
|
|
619
|
+
guardCollections.map((c) => ` - ${c.name} (ID: ${c.id})`).join("\n") +
|
|
620
|
+
`\n\nAsk the user: Which collection should "${params.name}" go in?`);
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
content: [
|
|
624
|
+
{
|
|
625
|
+
type: "text",
|
|
626
|
+
text: sections.join("\n"),
|
|
627
|
+
},
|
|
628
|
+
],
|
|
629
|
+
};
|
|
559
630
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
631
|
+
const { source_code, source_filename, ...registerParams } = params;
|
|
632
|
+
const result = await client.registerPrototype(registerParams);
|
|
633
|
+
const protoId = result.prototype?.id;
|
|
634
|
+
// Upload source code if provided
|
|
635
|
+
let sourceInfo = "";
|
|
636
|
+
if (source_code && protoId) {
|
|
637
|
+
try {
|
|
638
|
+
const sourceResult = await client.uploadSource(protoId, source_code, source_filename);
|
|
639
|
+
sourceInfo = `\nSource code uploaded (${sourceResult.source?.size || source_code.length} chars). Colleagues can download it from the prototype page.`;
|
|
640
|
+
}
|
|
641
|
+
catch (err) {
|
|
642
|
+
sourceInfo = `\nWarning: Source upload failed: ${err instanceof Error ? err.message : "Unknown error"}`;
|
|
643
|
+
}
|
|
564
644
|
}
|
|
565
645
|
return {
|
|
566
646
|
content: [
|
|
567
647
|
{
|
|
568
648
|
type: "text",
|
|
569
|
-
text:
|
|
649
|
+
text: `Prototype registered successfully!\n\nName: ${result.prototype?.name || params.name}\nVibeSharing URL: ${VIBESHARING_URL}/dashboard/projects/${protoId}\n${params.external_url ? `Live URL: ${params.external_url}` : ""}${sourceInfo}\n\nYour team can now view and leave feedback on this prototype.`,
|
|
570
650
|
},
|
|
571
651
|
],
|
|
572
652
|
};
|
|
573
653
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
const sourceResult = await client.uploadSource(protoId, source_code, source_filename);
|
|
582
|
-
sourceInfo = `\nSource code uploaded (${sourceResult.source?.size || source_code.length} chars). Colleagues can download it from the prototype page.`;
|
|
654
|
+
case "list_prototypes": {
|
|
655
|
+
const { search: protoSearch } = (args || {});
|
|
656
|
+
const result = await client.listPrototypes();
|
|
657
|
+
let prototypes = result.prototypes || [];
|
|
658
|
+
if (protoSearch) {
|
|
659
|
+
const matches = fuzzyMatch(protoSearch, prototypes, (p) => p.name);
|
|
660
|
+
prototypes = matches.map((m) => m.item);
|
|
583
661
|
}
|
|
584
|
-
|
|
585
|
-
|
|
662
|
+
if (prototypes.length === 0) {
|
|
663
|
+
return {
|
|
664
|
+
content: [
|
|
665
|
+
{
|
|
666
|
+
type: "text",
|
|
667
|
+
text: protoSearch
|
|
668
|
+
? `No prototypes matching "${protoSearch}". Use list_prototypes without search to see all.`
|
|
669
|
+
: "No prototypes found. Use register_prototype to add your first one!",
|
|
670
|
+
},
|
|
671
|
+
],
|
|
672
|
+
};
|
|
586
673
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
{
|
|
591
|
-
type: "text",
|
|
592
|
-
text: `Prototype registered successfully!\n\nName: ${result.prototype?.name || params.name}\nVibeSharing URL: ${VIBESHARING_URL}/dashboard/projects/${protoId}\n${params.external_url ? `Live URL: ${params.external_url}` : ""}${sourceInfo}\n\nYour team can now view and leave feedback on this prototype.`,
|
|
593
|
-
},
|
|
594
|
-
],
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
case "list_prototypes": {
|
|
598
|
-
const { search: protoSearch } = (args || {});
|
|
599
|
-
const result = await client.listPrototypes();
|
|
600
|
-
let prototypes = result.prototypes || [];
|
|
601
|
-
if (protoSearch) {
|
|
602
|
-
const matches = fuzzyMatch(protoSearch, prototypes, (p) => p.name);
|
|
603
|
-
prototypes = matches.map((m) => m.item);
|
|
604
|
-
}
|
|
605
|
-
if (prototypes.length === 0) {
|
|
674
|
+
const list = prototypes
|
|
675
|
+
.map((p) => `- ${p.name}\n ID: ${p.id}\n ${p.external_url ? `URL: ${p.external_url}\n ` : ""}Updated: ${new Date(p.updated_at).toLocaleDateString()}`)
|
|
676
|
+
.join("\n\n");
|
|
606
677
|
return {
|
|
607
678
|
content: [
|
|
608
679
|
{
|
|
609
680
|
type: "text",
|
|
610
|
-
text: protoSearch
|
|
611
|
-
? `No prototypes matching "${protoSearch}". Use list_prototypes without search to see all.`
|
|
612
|
-
: "No prototypes found. Use register_prototype to add your first one!",
|
|
681
|
+
text: `Found ${prototypes.length} prototype(s)${protoSearch ? ` matching "${protoSearch}"` : ""}:\n\n${list}`,
|
|
613
682
|
},
|
|
614
683
|
],
|
|
615
684
|
};
|
|
616
685
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
.
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
686
|
+
case "list_collections": {
|
|
687
|
+
const { search: collSearch } = (args || {});
|
|
688
|
+
const result = await client.listCollections();
|
|
689
|
+
let collections = result.collections || [];
|
|
690
|
+
if (collSearch) {
|
|
691
|
+
const matches = fuzzyMatch(collSearch, collections, (c) => c.name);
|
|
692
|
+
collections = matches.map((m) => m.item);
|
|
693
|
+
}
|
|
694
|
+
if (collections.length === 0) {
|
|
695
|
+
return {
|
|
696
|
+
content: [
|
|
697
|
+
{
|
|
698
|
+
type: "text",
|
|
699
|
+
text: collSearch
|
|
700
|
+
? `No collections matching "${collSearch}". Use list_collections without search to see all.`
|
|
701
|
+
: "No collections found. Create one in the VibeSharing dashboard first.",
|
|
702
|
+
},
|
|
703
|
+
],
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
const list = collections
|
|
707
|
+
.map((c) => `- ${c.name}\n ID: ${c.id}${c.description ? `\n ${c.description}` : ""}`)
|
|
708
|
+
.join("\n\n");
|
|
638
709
|
return {
|
|
639
710
|
content: [
|
|
640
711
|
{
|
|
641
712
|
type: "text",
|
|
642
|
-
text: collSearch
|
|
643
|
-
? `No collections matching "${collSearch}". Use list_collections without search to see all.`
|
|
644
|
-
: "No collections found. Create one in the VibeSharing dashboard first.",
|
|
713
|
+
text: `Found ${collections.length} collection(s)${collSearch ? ` matching "${collSearch}"` : ""}:\n\n${list}`,
|
|
645
714
|
},
|
|
646
715
|
],
|
|
647
716
|
};
|
|
648
717
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
.
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
{
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
718
|
+
case "get_feedback": {
|
|
719
|
+
const { project_id } = args;
|
|
720
|
+
const result = await client.getFeedback(project_id);
|
|
721
|
+
const feedback = result.feedback || [];
|
|
722
|
+
if (feedback.length === 0) {
|
|
723
|
+
return {
|
|
724
|
+
content: [
|
|
725
|
+
{
|
|
726
|
+
type: "text",
|
|
727
|
+
text: "No feedback yet for this prototype. Share it with your team to get their thoughts!",
|
|
728
|
+
},
|
|
729
|
+
],
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
const feedbackList = feedback
|
|
733
|
+
.map((f) => {
|
|
734
|
+
const status = f.resolved_at ? " [Resolved]" : "";
|
|
735
|
+
const replies = f.replies && f.replies.length > 0
|
|
736
|
+
? `\n Replies: ${f.replies.length}`
|
|
737
|
+
: "";
|
|
738
|
+
return `- ${f.user_name}${status}: "${f.content}"\n ${new Date(f.created_at).toLocaleDateString()}${replies}`;
|
|
739
|
+
})
|
|
740
|
+
.join("\n\n");
|
|
666
741
|
return {
|
|
667
742
|
content: [
|
|
668
743
|
{
|
|
669
744
|
type: "text",
|
|
670
|
-
text:
|
|
745
|
+
text: `Feedback (${feedback.length} items):\n\n${feedbackList}`,
|
|
671
746
|
},
|
|
672
747
|
],
|
|
673
748
|
};
|
|
674
749
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
const replies = f.replies && f.replies.length > 0
|
|
679
|
-
? `\n Replies: ${f.replies.length}`
|
|
680
|
-
: "";
|
|
681
|
-
return `- ${f.user_name}${status}: "${f.content}"\n ${new Date(f.created_at).toLocaleDateString()}${replies}`;
|
|
682
|
-
})
|
|
683
|
-
.join("\n\n");
|
|
684
|
-
return {
|
|
685
|
-
content: [
|
|
686
|
-
{
|
|
687
|
-
type: "text",
|
|
688
|
-
text: `Feedback (${feedback.length} items):\n\n${feedbackList}`,
|
|
689
|
-
},
|
|
690
|
-
],
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
case "sync_context": {
|
|
694
|
-
const { project_id, content } = args;
|
|
695
|
-
await client.syncContext(project_id, content);
|
|
696
|
-
return {
|
|
697
|
-
content: [
|
|
698
|
-
{
|
|
699
|
-
type: "text",
|
|
700
|
-
text: `Context synced successfully to VibeSharing!\n\nProject ID: ${project_id}\nContent length: ${content.length} characters\n\nThis context will be available to team members viewing the prototype.`,
|
|
701
|
-
},
|
|
702
|
-
],
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
case "verify_token": {
|
|
706
|
-
const result = await client.verifyToken();
|
|
707
|
-
if (result.valid) {
|
|
750
|
+
case "sync_context": {
|
|
751
|
+
const { project_id, content } = args;
|
|
752
|
+
await client.syncContext(project_id, content);
|
|
708
753
|
return {
|
|
709
754
|
content: [
|
|
710
755
|
{
|
|
711
756
|
type: "text",
|
|
712
|
-
text: `
|
|
757
|
+
text: `Context synced successfully to VibeSharing!\n\nProject ID: ${project_id}\nContent length: ${content.length} characters\n\nThis context will be available to team members viewing the prototype.`,
|
|
713
758
|
},
|
|
714
759
|
],
|
|
715
760
|
};
|
|
716
761
|
}
|
|
717
|
-
|
|
762
|
+
case "verify_token": {
|
|
763
|
+
const result = await client.verifyToken();
|
|
764
|
+
if (result.valid) {
|
|
765
|
+
return {
|
|
766
|
+
content: [
|
|
767
|
+
{
|
|
768
|
+
type: "text",
|
|
769
|
+
text: `Token is valid! Connected to ${VIBESHARING_URL}\n\nYour organization has ${result.prototypeCount} prototype(s).`,
|
|
770
|
+
},
|
|
771
|
+
],
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
return {
|
|
776
|
+
content: [
|
|
777
|
+
{
|
|
778
|
+
type: "text",
|
|
779
|
+
text: `Token is invalid: ${result.error}\n\nGet a new deploy token from VibeSharing → Dashboard → Account Settings.\nThen update your MCP config:\n claude mcp remove vibesharing\n claude mcp add vibesharing -e VIBESHARING_TOKEN=vs_YOUR_NEW_TOKEN -- node /path/to/mcp-server/dist/index.js`,
|
|
780
|
+
},
|
|
781
|
+
],
|
|
782
|
+
isError: true,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
case "upload_source": {
|
|
787
|
+
const { prototype_id, source_code, filename, storage_option } = args;
|
|
788
|
+
const result = await client.uploadSource(prototype_id, source_code, filename, storage_option);
|
|
718
789
|
return {
|
|
719
790
|
content: [
|
|
720
791
|
{
|
|
721
792
|
type: "text",
|
|
722
|
-
text: `
|
|
793
|
+
text: `Source code uploaded successfully!\n\nPrototype ID: ${prototype_id}\nFilename: ${result.source?.filename || filename || "page.tsx"}\nSize: ${result.source?.size || source_code.length} characters\nStorage: ${result.source?.storage_option || storage_option || "permanent"}\n\nColleagues can download this from: ${VIBESHARING_URL}/dashboard/projects/${prototype_id}`,
|
|
723
794
|
},
|
|
724
795
|
],
|
|
725
|
-
isError: true,
|
|
726
796
|
};
|
|
727
797
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
798
|
+
case "deploy_prototype": {
|
|
799
|
+
const { code, name, prototype_id } = args;
|
|
800
|
+
// Guardrail: if no existing prototype specified, bounce back with options
|
|
801
|
+
if (!prototype_id) {
|
|
802
|
+
const [guardCollResult, guardProtoResult] = await Promise.all([
|
|
803
|
+
client.listCollections(),
|
|
804
|
+
client.listPrototypes(),
|
|
805
|
+
]);
|
|
806
|
+
const guardCollections = guardCollResult.collections || [];
|
|
807
|
+
const guardPrototypes = guardProtoResult.prototypes || [];
|
|
808
|
+
const similarProjects = fuzzyMatch(name, guardPrototypes, (p) => p.name).slice(0, 5);
|
|
809
|
+
const sections = [
|
|
810
|
+
`HOLD ON — confirm with the user before deploying "${name}":\n`,
|
|
811
|
+
];
|
|
812
|
+
if (similarProjects.length > 0) {
|
|
813
|
+
sections.push(`Similar existing projects:\n` +
|
|
814
|
+
similarProjects.map((m) => {
|
|
815
|
+
const p = m.item;
|
|
816
|
+
return ` - "${p.name}" (ID: ${p.id})${p.external_url ? ` → ${p.external_url}` : ""}`;
|
|
817
|
+
}).join("\n") +
|
|
818
|
+
`\n\nAsk the user: Is this an UPDATE to one of these, or a NEW project?`);
|
|
819
|
+
}
|
|
820
|
+
if (guardCollections.length > 0) {
|
|
821
|
+
sections.push(`\nAvailable collections:\n` +
|
|
822
|
+
guardCollections.map((c) => ` - ${c.name} (ID: ${c.id})`).join("\n") +
|
|
823
|
+
`\n\nAsk the user: Which collection should this go in?`);
|
|
824
|
+
}
|
|
825
|
+
const suggestedSlug = name
|
|
826
|
+
.toLowerCase()
|
|
827
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
828
|
+
.replace(/^-+|-+$/g, "");
|
|
829
|
+
sections.push(`\nSuggested deploy name: "${suggestedSlug}" → https://${suggestedSlug}.vercel.app\n` +
|
|
830
|
+
`Ask the user: Do you want to use this name or choose a different one?`);
|
|
831
|
+
return {
|
|
832
|
+
content: [
|
|
833
|
+
{
|
|
834
|
+
type: "text",
|
|
835
|
+
text: sections.join("\n"),
|
|
836
|
+
},
|
|
837
|
+
],
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
// If no prototype_id, first register a new prototype
|
|
841
|
+
let prototypeId = prototype_id;
|
|
842
|
+
if (!prototypeId) {
|
|
843
|
+
const registered = await client.registerPrototype({ name });
|
|
844
|
+
prototypeId = registered.prototype?.id;
|
|
762
845
|
}
|
|
763
|
-
if (
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
846
|
+
if (!prototypeId) {
|
|
847
|
+
return {
|
|
848
|
+
content: [
|
|
849
|
+
{
|
|
850
|
+
type: "text",
|
|
851
|
+
text: "Error: Could not create prototype. Please try again.",
|
|
852
|
+
},
|
|
853
|
+
],
|
|
854
|
+
isError: true,
|
|
855
|
+
};
|
|
767
856
|
}
|
|
768
|
-
const
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
`Ask the user: Do you want to use this name or choose a different one?`);
|
|
857
|
+
const result = await client.deployPrototype({
|
|
858
|
+
code,
|
|
859
|
+
prototypeName: name,
|
|
860
|
+
prototypeId,
|
|
861
|
+
});
|
|
774
862
|
return {
|
|
775
863
|
content: [
|
|
776
864
|
{
|
|
777
865
|
type: "text",
|
|
778
|
-
text:
|
|
866
|
+
text: `Deployed successfully!\n\nLive URL: ${result.deployedUrl}\nVibeSharing: ${VIBESHARING_URL}/dashboard/projects/${prototypeId}\n\nYour team can now view the prototype and leave feedback.${result.contextImported ? "\n\nProject context was automatically imported." : ""}`,
|
|
779
867
|
},
|
|
780
868
|
],
|
|
781
869
|
};
|
|
782
870
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
871
|
+
case "create_collection": {
|
|
872
|
+
const { name: collName, description: collDesc } = args;
|
|
873
|
+
const collResult = await client.createCollection({
|
|
874
|
+
name: collName,
|
|
875
|
+
description: collDesc,
|
|
876
|
+
});
|
|
877
|
+
const coll = collResult.collection;
|
|
790
878
|
return {
|
|
791
879
|
content: [
|
|
792
880
|
{
|
|
793
881
|
type: "text",
|
|
794
|
-
text:
|
|
882
|
+
text: `Collection created!\n\nName: ${coll.name}\nID: ${coll.id}\nSlug: ${coll.slug}\n\nYou can now use this collection_id when registering prototypes with register_prototype.`,
|
|
795
883
|
},
|
|
796
884
|
],
|
|
797
|
-
isError: true,
|
|
798
885
|
};
|
|
799
886
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
prototypeId,
|
|
804
|
-
});
|
|
805
|
-
return {
|
|
806
|
-
content: [
|
|
807
|
-
{
|
|
808
|
-
type: "text",
|
|
809
|
-
text: `Deployed successfully!\n\nLive URL: ${result.deployedUrl}\nVibeSharing: ${VIBESHARING_URL}/dashboard/projects/${prototypeId}\n\nYour team can now view the prototype and leave feedback.${result.contextImported ? "\n\nProject context was automatically imported." : ""}`,
|
|
810
|
-
},
|
|
811
|
-
],
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
case "create_collection": {
|
|
815
|
-
const { name: collName, description: collDesc } = args;
|
|
816
|
-
const collResult = await client.createCollection({
|
|
817
|
-
name: collName,
|
|
818
|
-
description: collDesc,
|
|
819
|
-
});
|
|
820
|
-
const coll = collResult.collection;
|
|
821
|
-
return {
|
|
822
|
-
content: [
|
|
823
|
-
{
|
|
824
|
-
type: "text",
|
|
825
|
-
text: `Collection created!\n\nName: ${coll.name}\nID: ${coll.id}\nSlug: ${coll.slug}\n\nYou can now use this collection_id when registering prototypes with register_prototype.`,
|
|
826
|
-
},
|
|
827
|
-
],
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
case "deploy_files": {
|
|
831
|
-
const { prototype_id: deployProtoId, files, commit_message, deploy_name: deployName } = args;
|
|
832
|
-
const deployResult = await client.deployFiles(deployProtoId, files, commit_message || "Deploy via MCP", deployName);
|
|
833
|
-
return {
|
|
834
|
-
content: [
|
|
835
|
-
{
|
|
836
|
-
type: "text",
|
|
837
|
-
text: `Deployed ${files.length} files!\n\n${deployResult.deployName ? `Deploy name: ${deployResult.deployName}\n` : ""}Live URL: ${deployResult.deployUrl || "Deploying..."}\nRepo: ${deployResult.repoUrl || "N/A"}\nCommit: ${deployResult.commitSha || "N/A"}\n\nVibeSharing: ${VIBESHARING_URL}/dashboard/projects/${deployProtoId}\n\nYour team can now view the prototype and leave feedback.`,
|
|
838
|
-
},
|
|
839
|
-
],
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
case "import_repo": {
|
|
843
|
-
const { repo_url, name: importName, prototype_id: importProtoId, collection_id: importCollectionId, parent_project_id: importParentId, description: importDesc, deploy_name: deployName, } = args;
|
|
844
|
-
// Guardrail: if no collection and no existing prototype specified, bounce back with options
|
|
845
|
-
if (!importCollectionId && !importProtoId) {
|
|
846
|
-
const [guardCollResult, guardProtoResult] = await Promise.all([
|
|
847
|
-
client.listCollections(),
|
|
848
|
-
client.listPrototypes(),
|
|
849
|
-
]);
|
|
850
|
-
const guardCollections = guardCollResult.collections || [];
|
|
851
|
-
const guardPrototypes = guardProtoResult.prototypes || [];
|
|
852
|
-
const repoName = repo_url.replace(/\.git$/, "").split("/").pop() || "imported-prototype";
|
|
853
|
-
const suggestedName = importName || repoName.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
854
|
-
// Check for similar existing prototypes
|
|
855
|
-
const similarProjects = importName
|
|
856
|
-
? fuzzyMatch(importName, guardPrototypes, (p) => p.name).slice(0, 5)
|
|
857
|
-
: [];
|
|
858
|
-
const sections = [
|
|
859
|
-
`HOLD ON — confirm these details with the user before deploying:\n`,
|
|
860
|
-
`Prototype name: "${suggestedName}"`,
|
|
861
|
-
];
|
|
862
|
-
if (similarProjects.length > 0) {
|
|
863
|
-
sections.push(`\nSimilar existing projects found:\n` +
|
|
864
|
-
similarProjects.map((m) => {
|
|
865
|
-
const p = m.item;
|
|
866
|
-
return ` - "${p.name}" (ID: ${p.id})${p.external_url ? ` → ${p.external_url}` : ""}`;
|
|
867
|
-
}).join("\n") +
|
|
868
|
-
`\n\nAsk the user: Is this an UPDATE to one of these existing projects, or a NEW project?`);
|
|
869
|
-
}
|
|
870
|
-
if (guardCollections.length > 0) {
|
|
871
|
-
sections.push(`\nNo collection specified. Available collections:\n` +
|
|
872
|
-
guardCollections.map((c) => ` - ${c.name} (ID: ${c.id})`).join("\n") +
|
|
873
|
-
`\n\nAsk the user: Which collection should "${suggestedName}" go in?`);
|
|
874
|
-
}
|
|
875
|
-
if (!deployName) {
|
|
876
|
-
const suggestedSlug = suggestedName
|
|
877
|
-
.toLowerCase()
|
|
878
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
879
|
-
.replace(/^-+|-+$/g, "");
|
|
880
|
-
sections.push(`\nNo deploy name specified. Suggested: "${suggestedSlug}" → https://${suggestedSlug}.vercel.app\n` +
|
|
881
|
-
`Ask the user: Do you want to use "${suggestedSlug}" or choose a different deploy name?`);
|
|
882
|
-
}
|
|
887
|
+
case "deploy_files": {
|
|
888
|
+
const { prototype_id: deployProtoId, files, commit_message, deploy_name: deployName } = args;
|
|
889
|
+
const deployResult = await client.deployFiles(deployProtoId, files, commit_message || "Deploy via MCP", deployName);
|
|
883
890
|
return {
|
|
884
891
|
content: [
|
|
885
892
|
{
|
|
886
893
|
type: "text",
|
|
887
|
-
text:
|
|
894
|
+
text: `Deployed ${files.length} files!\n\n${deployResult.deployName ? `Deploy name: ${deployResult.deployName}\n` : ""}Live URL: ${deployResult.deployUrl || "Deploying..."}\nRepo: ${deployResult.repoUrl || "N/A"}\nCommit: ${deployResult.commitSha || "N/A"}\n\nVibeSharing: ${VIBESHARING_URL}/dashboard/projects/${deployProtoId}\n\nYour team can now view the prototype and leave feedback.`,
|
|
888
895
|
},
|
|
889
896
|
],
|
|
890
897
|
};
|
|
891
898
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
899
|
+
case "import_repo": {
|
|
900
|
+
const { repo_url, name: importName, prototype_id: importProtoId, collection_id: importCollectionId, parent_project_id: importParentId, description: importDesc, deploy_name: deployName, } = args;
|
|
901
|
+
// Guardrail: if no collection and no existing prototype specified, bounce back with options
|
|
902
|
+
if (!importCollectionId && !importProtoId) {
|
|
903
|
+
const [guardCollResult, guardProtoResult] = await Promise.all([
|
|
904
|
+
client.listCollections(),
|
|
905
|
+
client.listPrototypes(),
|
|
906
|
+
]);
|
|
907
|
+
const guardCollections = guardCollResult.collections || [];
|
|
908
|
+
const guardPrototypes = guardProtoResult.prototypes || [];
|
|
909
|
+
const repoName = repo_url.replace(/\.git$/, "").split("/").pop() || "imported-prototype";
|
|
910
|
+
const suggestedName = importName || repoName.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
911
|
+
// Check for similar existing prototypes
|
|
912
|
+
const similarProjects = importName
|
|
913
|
+
? fuzzyMatch(importName, guardPrototypes, (p) => p.name).slice(0, 5)
|
|
914
|
+
: [];
|
|
915
|
+
const sections = [
|
|
916
|
+
`HOLD ON — confirm these details with the user before deploying:\n`,
|
|
917
|
+
`Prototype name: "${suggestedName}"`,
|
|
918
|
+
];
|
|
919
|
+
if (similarProjects.length > 0) {
|
|
920
|
+
sections.push(`\nSimilar existing projects found:\n` +
|
|
921
|
+
similarProjects.map((m) => {
|
|
922
|
+
const p = m.item;
|
|
923
|
+
return ` - "${p.name}" (ID: ${p.id})${p.external_url ? ` → ${p.external_url}` : ""}`;
|
|
924
|
+
}).join("\n") +
|
|
925
|
+
`\n\nAsk the user: Is this an UPDATE to one of these existing projects, or a NEW project?`);
|
|
926
|
+
}
|
|
927
|
+
if (guardCollections.length > 0) {
|
|
928
|
+
sections.push(`\nNo collection specified. Available collections:\n` +
|
|
929
|
+
guardCollections.map((c) => ` - ${c.name} (ID: ${c.id})`).join("\n") +
|
|
930
|
+
`\n\nAsk the user: Which collection should "${suggestedName}" go in?`);
|
|
931
|
+
}
|
|
932
|
+
if (!deployName) {
|
|
933
|
+
const suggestedSlug = suggestedName
|
|
934
|
+
.toLowerCase()
|
|
935
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
936
|
+
.replace(/^-+|-+$/g, "");
|
|
937
|
+
sections.push(`\nNo deploy name specified. Suggested: "${suggestedSlug}" → https://${suggestedSlug}.vercel.app\n` +
|
|
938
|
+
`Ask the user: Do you want to use "${suggestedSlug}" or choose a different deploy name?`);
|
|
939
|
+
}
|
|
940
|
+
return {
|
|
941
|
+
content: [
|
|
942
|
+
{
|
|
943
|
+
type: "text",
|
|
944
|
+
text: sections.join("\n"),
|
|
945
|
+
},
|
|
946
|
+
],
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
// Auto-create prototype if no ID provided (collection was confirmed above)
|
|
950
|
+
let protoId = importProtoId;
|
|
951
|
+
if (!protoId) {
|
|
952
|
+
// Derive name from repo URL if not provided
|
|
953
|
+
const repoName = repo_url.replace(/\.git$/, "").split("/").pop() || "imported-prototype";
|
|
954
|
+
const protoName = importName || repoName.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
955
|
+
const registered = await client.registerPrototype({
|
|
956
|
+
name: protoName,
|
|
957
|
+
description: importDesc || `Imported from ${repo_url}`,
|
|
958
|
+
external_url: repo_url,
|
|
959
|
+
collection_id: importCollectionId,
|
|
960
|
+
parent_project_id: importParentId,
|
|
961
|
+
});
|
|
962
|
+
protoId = registered.prototype?.id;
|
|
963
|
+
}
|
|
964
|
+
if (!protoId) {
|
|
965
|
+
return {
|
|
966
|
+
content: [
|
|
967
|
+
{
|
|
968
|
+
type: "text",
|
|
969
|
+
text: "Error: Could not create prototype. Please try again.",
|
|
970
|
+
},
|
|
971
|
+
],
|
|
972
|
+
isError: true,
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
const importResult = await client.importRepo(protoId, repo_url, deployName);
|
|
908
976
|
return {
|
|
909
977
|
content: [
|
|
910
978
|
{
|
|
911
979
|
type: "text",
|
|
912
|
-
text:
|
|
980
|
+
text: `Repo imported and deploying!\n\n${importResult.deployName ? `Deploy name: ${importResult.deployName}\n` : ""}Live URL: ${importResult.deployUrl}\nRepo: ${importResult.repoUrl}\nVibeSharing: ${VIBESHARING_URL}/dashboard/projects/${protoId}\nFiles imported: ${importResult.fileCount || "unknown"}\n\nPushes to the VibeSharing repo will auto-deploy to Vercel.`,
|
|
913
981
|
},
|
|
914
982
|
],
|
|
915
|
-
isError: true,
|
|
916
983
|
};
|
|
917
984
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
]
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
const allPrototypes = protoResult.prototypes || [];
|
|
937
|
-
const sections = [];
|
|
938
|
-
// --- Collection matching ---
|
|
939
|
-
if (collName) {
|
|
940
|
-
const collMatches = fuzzyMatch(collName, allCollections, (c) => c.name);
|
|
941
|
-
if (collMatches.length === 0) {
|
|
942
|
-
sections.push(`COLLECTION: No match for "${collName}".\n` +
|
|
943
|
-
`Available collections:\n` +
|
|
944
|
-
allCollections
|
|
945
|
-
.map((c) => ` - ${c.name} (ID: ${c.id})`)
|
|
946
|
-
.join("\n") +
|
|
947
|
-
`\n\nAsk the user: Which collection should this go in? Or create a new one?`);
|
|
948
|
-
}
|
|
949
|
-
else if (collMatches[0].score >= 0.9) {
|
|
950
|
-
const best = collMatches[0].item;
|
|
951
|
-
sections.push(`COLLECTION: Matched "${collName}" → "${best.name}" (ID: ${best.id})`);
|
|
952
|
-
// Show prototypes in this collection for context
|
|
953
|
-
const collProtos = allPrototypes.filter((p) => p.folder_id === best.id);
|
|
954
|
-
if (collProtos.length > 0) {
|
|
955
|
-
sections.push(`Existing projects in "${best.name}":\n` +
|
|
956
|
-
collProtos
|
|
957
|
-
.map((p) => ` - ${p.name} (ID: ${p.id})${p.external_url ? ` → ${p.external_url}` : ""}`)
|
|
985
|
+
case "resolve_target": {
|
|
986
|
+
const { collection_name: collName, project_name: projName, deploy_name: desiredDeployName, } = (args || {});
|
|
987
|
+
// Fetch collections and prototypes in parallel
|
|
988
|
+
const [collResult, protoResult] = await Promise.all([
|
|
989
|
+
client.listCollections(),
|
|
990
|
+
client.listPrototypes(),
|
|
991
|
+
]);
|
|
992
|
+
const allCollections = collResult.collections || [];
|
|
993
|
+
const allPrototypes = protoResult.prototypes || [];
|
|
994
|
+
const sections = [];
|
|
995
|
+
// --- Collection matching ---
|
|
996
|
+
if (collName) {
|
|
997
|
+
const collMatches = fuzzyMatch(collName, allCollections, (c) => c.name);
|
|
998
|
+
if (collMatches.length === 0) {
|
|
999
|
+
sections.push(`COLLECTION: No match for "${collName}".\n` +
|
|
1000
|
+
`Available collections:\n` +
|
|
1001
|
+
allCollections
|
|
1002
|
+
.map((c) => ` - ${c.name} (ID: ${c.id})`)
|
|
958
1003
|
.join("\n") +
|
|
959
|
-
`\n\nAsk the user:
|
|
1004
|
+
`\n\nAsk the user: Which collection should this go in? Or create a new one?`);
|
|
1005
|
+
}
|
|
1006
|
+
else if (collMatches[0].score >= 0.9) {
|
|
1007
|
+
const best = collMatches[0].item;
|
|
1008
|
+
sections.push(`COLLECTION: Matched "${collName}" → "${best.name}" (ID: ${best.id})`);
|
|
1009
|
+
// Show prototypes in this collection for context
|
|
1010
|
+
const collProtos = allPrototypes.filter((p) => p.folder_id === best.id);
|
|
1011
|
+
if (collProtos.length > 0) {
|
|
1012
|
+
sections.push(`Existing projects in "${best.name}":\n` +
|
|
1013
|
+
collProtos
|
|
1014
|
+
.map((p) => ` - ${p.name} (ID: ${p.id})${p.external_url ? ` → ${p.external_url}` : ""}`)
|
|
1015
|
+
.join("\n") +
|
|
1016
|
+
`\n\nAsk the user: Should this be a NEW project in "${best.name}", or an update to one of these existing projects?`);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
// Fuzzy matches but not confident
|
|
1021
|
+
sections.push(`COLLECTION: "${collName}" is not an exact match. Did the user mean one of these?\n` +
|
|
1022
|
+
collMatches
|
|
1023
|
+
.slice(0, 5)
|
|
1024
|
+
.map((m) => {
|
|
1025
|
+
const c = m.item;
|
|
1026
|
+
return ` - "${c.name}" (ID: ${c.id}, confidence: ${Math.round(m.score * 100)}%)`;
|
|
1027
|
+
})
|
|
1028
|
+
.join("\n") +
|
|
1029
|
+
`\n\nAsk the user to confirm which collection.`);
|
|
960
1030
|
}
|
|
961
1031
|
}
|
|
962
1032
|
else {
|
|
963
|
-
//
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1033
|
+
// No collection name given — show all options
|
|
1034
|
+
if (allCollections.length > 0) {
|
|
1035
|
+
sections.push(`COLLECTION: Not specified. Available collections:\n` +
|
|
1036
|
+
allCollections
|
|
1037
|
+
.map((c) => ` - ${c.name} (ID: ${c.id})`)
|
|
1038
|
+
.join("\n") +
|
|
1039
|
+
`\n\nAsk the user: Which collection should this prototype go in? Or create a new one?`);
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
sections.push(`COLLECTION: No collections exist yet. Ask the user if they want to create one.`);
|
|
1043
|
+
}
|
|
973
1044
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1045
|
+
// --- Project matching ---
|
|
1046
|
+
if (projName) {
|
|
1047
|
+
const projMatches = fuzzyMatch(projName, allPrototypes, (p) => p.name);
|
|
1048
|
+
if (projMatches.length === 0) {
|
|
1049
|
+
sections.push(`PROJECT: No match for "${projName}". This will be a new project.\n` +
|
|
1050
|
+
`Ask the user to confirm the name for the new project.`);
|
|
1051
|
+
}
|
|
1052
|
+
else if (projMatches[0].score >= 0.9) {
|
|
1053
|
+
const best = projMatches[0].item;
|
|
1054
|
+
sections.push(`PROJECT: Matched "${projName}" → "${best.name}" (ID: ${best.id})${best.external_url ? `\n Current URL: ${best.external_url}` : ""}\n\n` +
|
|
1055
|
+
`Ask the user: Deploy as an UPDATE to "${best.name}", or create a NEW project?`);
|
|
1056
|
+
}
|
|
1057
|
+
else {
|
|
1058
|
+
sections.push(`PROJECT: "${projName}" is not an exact match. Close matches:\n` +
|
|
1059
|
+
projMatches
|
|
1060
|
+
.slice(0, 5)
|
|
1061
|
+
.map((m) => {
|
|
1062
|
+
const p = m.item;
|
|
1063
|
+
return ` - "${p.name}" (ID: ${p.id}, confidence: ${Math.round(m.score * 100)}%)`;
|
|
1064
|
+
})
|
|
1065
|
+
.join("\n") +
|
|
1066
|
+
`\n\nAsk the user: Is this an update to one of these, or a new project?`);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
// --- Deploy name ---
|
|
1070
|
+
if (desiredDeployName) {
|
|
1071
|
+
const slug = desiredDeployName
|
|
1072
|
+
.toLowerCase()
|
|
1073
|
+
.replace(/[^a-z0-9]/g, "-")
|
|
1074
|
+
.replace(/-{2,}/g, "-")
|
|
1075
|
+
.replace(/^-+|-+$/g, "");
|
|
1076
|
+
sections.push(`DEPLOY NAME: "${slug}" → will deploy to https://${slug}.vercel.app\n` +
|
|
1077
|
+
`(Availability will be checked at deploy time. If taken, a suffix will be added.)\n\n` +
|
|
1078
|
+
`Ask the user: Do you want to name this deployment "${slug}"?`);
|
|
983
1079
|
}
|
|
984
1080
|
else {
|
|
985
|
-
sections.push(`
|
|
1081
|
+
sections.push(`DEPLOY NAME: Not specified.\n` +
|
|
1082
|
+
`Ask the user: Do you want a custom deploy name (e.g., "erg-v3-teams" → erg-v3-teams.vercel.app), or auto-generate one?`);
|
|
986
1083
|
}
|
|
1084
|
+
return {
|
|
1085
|
+
content: [
|
|
1086
|
+
{
|
|
1087
|
+
type: "text",
|
|
1088
|
+
text: sections.join("\n\n---\n\n"),
|
|
1089
|
+
},
|
|
1090
|
+
],
|
|
1091
|
+
};
|
|
987
1092
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1093
|
+
case "add_context_link": {
|
|
1094
|
+
const { folder_id, project_id, title, url, note } = args;
|
|
1095
|
+
if (!folder_id && !project_id) {
|
|
1096
|
+
return {
|
|
1097
|
+
content: [
|
|
1098
|
+
{
|
|
1099
|
+
type: "text",
|
|
1100
|
+
text: "Error: Either folder_id or project_id is required.",
|
|
1101
|
+
},
|
|
1102
|
+
],
|
|
1103
|
+
isError: true,
|
|
1104
|
+
};
|
|
994
1105
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1106
|
+
const result = await client.addContextLink({
|
|
1107
|
+
folderId: folder_id,
|
|
1108
|
+
projectId: project_id,
|
|
1109
|
+
title,
|
|
1110
|
+
url,
|
|
1111
|
+
note,
|
|
1112
|
+
});
|
|
1113
|
+
const parentType = folder_id ? "collection" : "project";
|
|
1114
|
+
const parentId = folder_id || project_id;
|
|
1115
|
+
return {
|
|
1116
|
+
content: [
|
|
1117
|
+
{
|
|
1118
|
+
type: "text",
|
|
1119
|
+
text: `Reference link added successfully!\n\nTitle: ${title}${url ? `\nURL: ${url}` : ""}${note ? `\nNote: ${note}` : ""}\nAttached to ${parentType}: ${parentId}\nLink ID: ${result.link?.id}\n\nView it at: ${VIBESHARING_URL}/dashboard/${folder_id ? "folders" : "projects"}/${parentId}`,
|
|
1120
|
+
},
|
|
1121
|
+
],
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
case "list_context_links": {
|
|
1125
|
+
const { folder_id, project_id } = args;
|
|
1126
|
+
if (!folder_id && !project_id) {
|
|
1127
|
+
return {
|
|
1128
|
+
content: [
|
|
1129
|
+
{
|
|
1130
|
+
type: "text",
|
|
1131
|
+
text: "Error: Either folder_id or project_id is required.",
|
|
1132
|
+
},
|
|
1133
|
+
],
|
|
1134
|
+
isError: true,
|
|
1135
|
+
};
|
|
999
1136
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1137
|
+
const result = await client.listContextLinks({
|
|
1138
|
+
folderId: folder_id,
|
|
1139
|
+
projectId: project_id,
|
|
1140
|
+
});
|
|
1141
|
+
const links = result.links || [];
|
|
1142
|
+
if (links.length === 0) {
|
|
1143
|
+
return {
|
|
1144
|
+
content: [
|
|
1145
|
+
{
|
|
1146
|
+
type: "text",
|
|
1147
|
+
text: "No reference links found. Use add_context_link to attach Figma designs, PRDs, docs, or notes.",
|
|
1148
|
+
},
|
|
1149
|
+
],
|
|
1150
|
+
};
|
|
1010
1151
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
const slug = desiredDeployName
|
|
1015
|
-
.toLowerCase()
|
|
1016
|
-
.replace(/[^a-z0-9]/g, "-")
|
|
1017
|
-
.replace(/-{2,}/g, "-")
|
|
1018
|
-
.replace(/^-+|-+$/g, "");
|
|
1019
|
-
sections.push(`DEPLOY NAME: "${slug}" → will deploy to https://${slug}.vercel.app\n` +
|
|
1020
|
-
`(Availability will be checked at deploy time. If taken, a suffix will be added.)\n\n` +
|
|
1021
|
-
`Ask the user: Do you want to name this deployment "${slug}"?`);
|
|
1022
|
-
}
|
|
1023
|
-
else {
|
|
1024
|
-
sections.push(`DEPLOY NAME: Not specified.\n` +
|
|
1025
|
-
`Ask the user: Do you want a custom deploy name (e.g., "erg-v3-teams" → erg-v3-teams.vercel.app), or auto-generate one?`);
|
|
1026
|
-
}
|
|
1027
|
-
return {
|
|
1028
|
-
content: [
|
|
1029
|
-
{
|
|
1030
|
-
type: "text",
|
|
1031
|
-
text: sections.join("\n\n---\n\n"),
|
|
1032
|
-
},
|
|
1033
|
-
],
|
|
1034
|
-
};
|
|
1035
|
-
}
|
|
1036
|
-
case "add_context_link": {
|
|
1037
|
-
const { folder_id, project_id, title, url, note } = args;
|
|
1038
|
-
if (!folder_id && !project_id) {
|
|
1152
|
+
const list = links
|
|
1153
|
+
.map((l) => `- ${l.title}\n ID: ${l.id}${l.url ? `\n URL: ${l.url}` : " (note)"}${l.note ? `\n Note: ${l.note}` : ""}\n Added: ${new Date(l.created_at).toLocaleDateString()}`)
|
|
1154
|
+
.join("\n\n");
|
|
1039
1155
|
return {
|
|
1040
1156
|
content: [
|
|
1041
1157
|
{
|
|
1042
1158
|
type: "text",
|
|
1043
|
-
text:
|
|
1159
|
+
text: `Found ${links.length} reference link(s):\n\n${list}`,
|
|
1044
1160
|
},
|
|
1045
1161
|
],
|
|
1046
|
-
isError: true,
|
|
1047
1162
|
};
|
|
1048
1163
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
title,
|
|
1053
|
-
url,
|
|
1054
|
-
note,
|
|
1055
|
-
});
|
|
1056
|
-
const parentType = folder_id ? "collection" : "project";
|
|
1057
|
-
const parentId = folder_id || project_id;
|
|
1058
|
-
return {
|
|
1059
|
-
content: [
|
|
1060
|
-
{
|
|
1061
|
-
type: "text",
|
|
1062
|
-
text: `Reference link added successfully!\n\nTitle: ${title}${url ? `\nURL: ${url}` : ""}${note ? `\nNote: ${note}` : ""}\nAttached to ${parentType}: ${parentId}\nLink ID: ${result.link?.id}\n\nView it at: ${VIBESHARING_URL}/dashboard/${folder_id ? "folders" : "projects"}/${parentId}`,
|
|
1063
|
-
},
|
|
1064
|
-
],
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
case "list_context_links": {
|
|
1068
|
-
const { folder_id, project_id } = args;
|
|
1069
|
-
if (!folder_id && !project_id) {
|
|
1164
|
+
case "remove_context_link": {
|
|
1165
|
+
const { link_id } = args;
|
|
1166
|
+
await client.removeContextLink(link_id);
|
|
1070
1167
|
return {
|
|
1071
1168
|
content: [
|
|
1072
1169
|
{
|
|
1073
1170
|
type: "text",
|
|
1074
|
-
text:
|
|
1171
|
+
text: `Reference link ${link_id} removed successfully.`,
|
|
1075
1172
|
},
|
|
1076
1173
|
],
|
|
1077
|
-
isError: true,
|
|
1078
1174
|
};
|
|
1079
1175
|
}
|
|
1080
|
-
|
|
1081
|
-
folderId: folder_id,
|
|
1082
|
-
projectId: project_id,
|
|
1083
|
-
});
|
|
1084
|
-
const links = result.links || [];
|
|
1085
|
-
if (links.length === 0) {
|
|
1176
|
+
default:
|
|
1086
1177
|
return {
|
|
1087
1178
|
content: [
|
|
1088
1179
|
{
|
|
1089
1180
|
type: "text",
|
|
1090
|
-
text:
|
|
1181
|
+
text: `Unknown tool: ${name}`,
|
|
1091
1182
|
},
|
|
1092
1183
|
],
|
|
1184
|
+
isError: true,
|
|
1093
1185
|
};
|
|
1094
|
-
}
|
|
1095
|
-
const list = links
|
|
1096
|
-
.map((l) => `- ${l.title}\n ID: ${l.id}${l.url ? `\n URL: ${l.url}` : " (note)"}${l.note ? `\n Note: ${l.note}` : ""}\n Added: ${new Date(l.created_at).toLocaleDateString()}`)
|
|
1097
|
-
.join("\n\n");
|
|
1098
|
-
return {
|
|
1099
|
-
content: [
|
|
1100
|
-
{
|
|
1101
|
-
type: "text",
|
|
1102
|
-
text: `Found ${links.length} reference link(s):\n\n${list}`,
|
|
1103
|
-
},
|
|
1104
|
-
],
|
|
1105
|
-
};
|
|
1106
1186
|
}
|
|
1107
|
-
case "remove_context_link": {
|
|
1108
|
-
const { link_id } = args;
|
|
1109
|
-
await client.removeContextLink(link_id);
|
|
1110
|
-
return {
|
|
1111
|
-
content: [
|
|
1112
|
-
{
|
|
1113
|
-
type: "text",
|
|
1114
|
-
text: `Reference link ${link_id} removed successfully.`,
|
|
1115
|
-
},
|
|
1116
|
-
],
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
default:
|
|
1120
|
-
return {
|
|
1121
|
-
content: [
|
|
1122
|
-
{
|
|
1123
|
-
type: "text",
|
|
1124
|
-
text: `Unknown tool: ${name}`,
|
|
1125
|
-
},
|
|
1126
|
-
],
|
|
1127
|
-
isError: true,
|
|
1128
|
-
};
|
|
1129
1187
|
}
|
|
1188
|
+
catch (error) {
|
|
1189
|
+
return {
|
|
1190
|
+
content: [
|
|
1191
|
+
{
|
|
1192
|
+
type: "text",
|
|
1193
|
+
text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1194
|
+
},
|
|
1195
|
+
],
|
|
1196
|
+
isError: true,
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
})();
|
|
1200
|
+
// Prepend What's New to the first successful tool call after upgrade
|
|
1201
|
+
if (pendingWhatsNew && !toolResult.isError) {
|
|
1202
|
+
toolResult.content.unshift({ type: "text", text: pendingWhatsNew + "\n\n---\n" });
|
|
1203
|
+
pendingWhatsNew = null;
|
|
1130
1204
|
}
|
|
1131
|
-
|
|
1132
|
-
return {
|
|
1133
|
-
content: [
|
|
1134
|
-
{
|
|
1135
|
-
type: "text",
|
|
1136
|
-
text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1137
|
-
},
|
|
1138
|
-
],
|
|
1139
|
-
isError: true,
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1205
|
+
return toolResult;
|
|
1142
1206
|
});
|
|
1143
1207
|
// List available resources (prototypes as resources)
|
|
1144
1208
|
server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
|
package/package.json
CHANGED