@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.
Files changed (2) hide show
  1. package/dist/index.js +546 -482
  2. 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
- try {
537
- switch (name) {
538
- case "register_prototype": {
539
- const params = args;
540
- // Guardrail: if no collection specified, bounce back with options
541
- if (!params.collection_id) {
542
- const [guardCollResult, guardProtoResult] = await Promise.all([
543
- client.listCollections(),
544
- client.listPrototypes(),
545
- ]);
546
- const guardCollections = guardCollResult.collections || [];
547
- const guardPrototypes = guardProtoResult.prototypes || [];
548
- const similarProjects = fuzzyMatch(params.name, guardPrototypes, (p) => p.name).slice(0, 5);
549
- const sections = [
550
- `HOLD ON confirm with the user before registering "${params.name}":\n`,
551
- ];
552
- if (similarProjects.length > 0) {
553
- sections.push(`Similar existing projects:\n` +
554
- similarProjects.map((m) => {
555
- const p = m.item;
556
- return ` - "${p.name}" (ID: ${p.id})`;
557
- }).join("\n") +
558
- `\n\nAsk the user: Is this an update to one of these, or a new project?`);
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
- if (guardCollections.length > 0) {
561
- sections.push(`\nNo collection specified. Available collections:\n` +
562
- guardCollections.map((c) => ` - ${c.name} (ID: ${c.id})`).join("\n") +
563
- `\n\nAsk the user: Which collection should "${params.name}" go in?`);
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: sections.join("\n"),
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
- const { source_code, source_filename, ...registerParams } = params;
575
- const result = await client.registerPrototype(registerParams);
576
- const protoId = result.prototype?.id;
577
- // Upload source code if provided
578
- let sourceInfo = "";
579
- if (source_code && protoId) {
580
- try {
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
- catch (err) {
585
- sourceInfo = `\nWarning: Source upload failed: ${err instanceof Error ? err.message : "Unknown error"}`;
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
- return {
589
- content: [
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
- const list = prototypes
618
- .map((p) => `- ${p.name}\n ID: ${p.id}\n ${p.external_url ? `URL: ${p.external_url}\n ` : ""}Updated: ${new Date(p.updated_at).toLocaleDateString()}`)
619
- .join("\n\n");
620
- return {
621
- content: [
622
- {
623
- type: "text",
624
- text: `Found ${prototypes.length} prototype(s)${protoSearch ? ` matching "${protoSearch}"` : ""}:\n\n${list}`,
625
- },
626
- ],
627
- };
628
- }
629
- case "list_collections": {
630
- const { search: collSearch } = (args || {});
631
- const result = await client.listCollections();
632
- let collections = result.collections || [];
633
- if (collSearch) {
634
- const matches = fuzzyMatch(collSearch, collections, (c) => c.name);
635
- collections = matches.map((m) => m.item);
636
- }
637
- if (collections.length === 0) {
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
- const list = collections
650
- .map((c) => `- ${c.name}\n ID: ${c.id}${c.description ? `\n ${c.description}` : ""}`)
651
- .join("\n\n");
652
- return {
653
- content: [
654
- {
655
- type: "text",
656
- text: `Found ${collections.length} collection(s)${collSearch ? ` matching "${collSearch}"` : ""}:\n\n${list}`,
657
- },
658
- ],
659
- };
660
- }
661
- case "get_feedback": {
662
- const { project_id } = args;
663
- const result = await client.getFeedback(project_id);
664
- const feedback = result.feedback || [];
665
- if (feedback.length === 0) {
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: "No feedback yet for this prototype. Share it with your team to get their thoughts!",
745
+ text: `Feedback (${feedback.length} items):\n\n${feedbackList}`,
671
746
  },
672
747
  ],
673
748
  };
674
749
  }
675
- const feedbackList = feedback
676
- .map((f) => {
677
- const status = f.resolved_at ? " [Resolved]" : "";
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: `Token is valid! Connected to ${VIBESHARING_URL}\n\nYour organization has ${result.prototypeCount} prototype(s).`,
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
- else {
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: `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`,
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
- case "upload_source": {
730
- const { prototype_id, source_code, filename, storage_option } = args;
731
- const result = await client.uploadSource(prototype_id, source_code, filename, storage_option);
732
- return {
733
- content: [
734
- {
735
- type: "text",
736
- 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}`,
737
- },
738
- ],
739
- };
740
- }
741
- case "deploy_prototype": {
742
- const { code, name, prototype_id } = args;
743
- // Guardrail: if no existing prototype specified, bounce back with options
744
- if (!prototype_id) {
745
- const [guardCollResult, guardProtoResult] = await Promise.all([
746
- client.listCollections(),
747
- client.listPrototypes(),
748
- ]);
749
- const guardCollections = guardCollResult.collections || [];
750
- const guardPrototypes = guardProtoResult.prototypes || [];
751
- const similarProjects = fuzzyMatch(name, guardPrototypes, (p) => p.name).slice(0, 5);
752
- const sections = [
753
- `HOLD ON confirm with the user before deploying "${name}":\n`,
754
- ];
755
- if (similarProjects.length > 0) {
756
- sections.push(`Similar existing projects:\n` +
757
- similarProjects.map((m) => {
758
- const p = m.item;
759
- return ` - "${p.name}" (ID: ${p.id})${p.external_url ? ` ${p.external_url}` : ""}`;
760
- }).join("\n") +
761
- `\n\nAsk the user: Is this an UPDATE to one of these, or a NEW project?`);
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 (guardCollections.length > 0) {
764
- sections.push(`\nAvailable collections:\n` +
765
- guardCollections.map((c) => ` - ${c.name} (ID: ${c.id})`).join("\n") +
766
- `\n\nAsk the user: Which collection should this go in?`);
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 suggestedSlug = name
769
- .toLowerCase()
770
- .replace(/[^a-z0-9]+/g, "-")
771
- .replace(/^-+|-+$/g, "");
772
- sections.push(`\nSuggested deploy name: "${suggestedSlug}" → https://${suggestedSlug}.vercel.app\n` +
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: sections.join("\n"),
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
- // If no prototype_id, first register a new prototype
784
- let prototypeId = prototype_id;
785
- if (!prototypeId) {
786
- const registered = await client.registerPrototype({ name });
787
- prototypeId = registered.prototype?.id;
788
- }
789
- if (!prototypeId) {
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: "Error: Could not create prototype. Please try again.",
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
- const result = await client.deployPrototype({
801
- code,
802
- prototypeName: name,
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: sections.join("\n"),
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
- // Auto-create prototype if no ID provided (collection was confirmed above)
893
- let protoId = importProtoId;
894
- if (!protoId) {
895
- // Derive name from repo URL if not provided
896
- const repoName = repo_url.replace(/\.git$/, "").split("/").pop() || "imported-prototype";
897
- const protoName = importName || repoName.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
898
- const registered = await client.registerPrototype({
899
- name: protoName,
900
- description: importDesc || `Imported from ${repo_url}`,
901
- external_url: repo_url,
902
- collection_id: importCollectionId,
903
- parent_project_id: importParentId,
904
- });
905
- protoId = registered.prototype?.id;
906
- }
907
- if (!protoId) {
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: "Error: Could not create prototype. Please try again.",
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
- const importResult = await client.importRepo(protoId, repo_url, deployName);
919
- return {
920
- content: [
921
- {
922
- type: "text",
923
- 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.`,
924
- },
925
- ],
926
- };
927
- }
928
- case "resolve_target": {
929
- const { collection_name: collName, project_name: projName, deploy_name: desiredDeployName, } = (args || {});
930
- // Fetch collections and prototypes in parallel
931
- const [collResult, protoResult] = await Promise.all([
932
- client.listCollections(),
933
- client.listPrototypes(),
934
- ]);
935
- const allCollections = collResult.collections || [];
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: Should this be a NEW project in "${best.name}", or an update to one of these existing projects?`);
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
- // Fuzzy matches but not confident
964
- sections.push(`COLLECTION: "${collName}" is not an exact match. Did the user mean one of these?\n` +
965
- collMatches
966
- .slice(0, 5)
967
- .map((m) => {
968
- const c = m.item;
969
- return ` - "${c.name}" (ID: ${c.id}, confidence: ${Math.round(m.score * 100)}%)`;
970
- })
971
- .join("\n") +
972
- `\n\nAsk the user to confirm which collection.`);
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
- else {
976
- // No collection name given show all options
977
- if (allCollections.length > 0) {
978
- sections.push(`COLLECTION: Not specified. Available collections:\n` +
979
- allCollections
980
- .map((c) => ` - ${c.name} (ID: ${c.id})`)
981
- .join("\n") +
982
- `\n\nAsk the user: Which collection should this prototype go in? Or create a new one?`);
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(`COLLECTION: No collections exist yet. Ask the user if they want to create one.`);
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
- // --- Project matching ---
989
- if (projName) {
990
- const projMatches = fuzzyMatch(projName, allPrototypes, (p) => p.name);
991
- if (projMatches.length === 0) {
992
- sections.push(`PROJECT: No match for "${projName}". This will be a new project.\n` +
993
- `Ask the user to confirm the name for the new project.`);
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
- else if (projMatches[0].score >= 0.9) {
996
- const best = projMatches[0].item;
997
- sections.push(`PROJECT: Matched "${projName}" → "${best.name}" (ID: ${best.id})${best.external_url ? `\n Current URL: ${best.external_url}` : ""}\n\n` +
998
- `Ask the user: Deploy as an UPDATE to "${best.name}", or create a NEW project?`);
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
- else {
1001
- sections.push(`PROJECT: "${projName}" is not an exact match. Close matches:\n` +
1002
- projMatches
1003
- .slice(0, 5)
1004
- .map((m) => {
1005
- const p = m.item;
1006
- return ` - "${p.name}" (ID: ${p.id}, confidence: ${Math.round(m.score * 100)}%)`;
1007
- })
1008
- .join("\n") +
1009
- `\n\nAsk the user: Is this an update to one of these, or a new project?`);
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
- // --- Deploy name ---
1013
- if (desiredDeployName) {
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: "Error: Either folder_id or project_id is required.",
1159
+ text: `Found ${links.length} reference link(s):\n\n${list}`,
1044
1160
  },
1045
1161
  ],
1046
- isError: true,
1047
1162
  };
1048
1163
  }
1049
- const result = await client.addContextLink({
1050
- folderId: folder_id,
1051
- projectId: project_id,
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: "Error: Either folder_id or project_id is required.",
1171
+ text: `Reference link ${link_id} removed successfully.`,
1075
1172
  },
1076
1173
  ],
1077
- isError: true,
1078
1174
  };
1079
1175
  }
1080
- const result = await client.listContextLinks({
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: "No reference links found. Use add_context_link to attach Figma designs, PRDs, docs, or notes.",
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
- catch (error) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibesharingapp/mcp-server",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "MCP server for VibeSharing - register prototypes and get feedback directly from Claude Code",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",