@fil-technology/appmate-mcp 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +3 -0
  2. package/dist/tools.js +217 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -90,6 +90,9 @@ staging or self-hosted instances.
90
90
  | `publish_referral_flow` | Promote the referral draft live. |
91
91
  | `list_referrals` | Paginated referral graph (status, `source` [link or typed-code], referee, reward flags). |
92
92
  | `export_referrals_csv` | Return the full referral graph as a CSV string. |
93
+ | `get_link_page_flow` | Read published + draft link-page (link-in-bio) config. |
94
+ | `update_link_page_draft` | Replace the link-page draft (header, icon links, link list, theme). |
95
+ | `publish_link_page_flow` | Promote the link-page draft live (appmate.cloud/p/{appSlug}). |
93
96
 
94
97
  Each referrer gets a unique link **and** a short, human-readable code (e.g.
95
98
  `K7Q4-R9XP`); a friend redeems by tapping the link or **typing the code** (no
package/dist/tools.js CHANGED
@@ -175,7 +175,7 @@ export const updateWaitlistDraft = {
175
175
  " theme?: 'minimal' | 'gradient' | 'dark' | 'side_by_side',",
176
176
  " eyebrow?: string, // short chip above title, e.g. 'Coming soon · Q1 2026'",
177
177
  " accentColor?: string, // hex '#rrggbb'; tints button + chip + gradient blob",
178
- " bullets?: [ // 0–5 value-prop cards under the form",
178
+ " bullets?: [ // 0–5 value-prop cards, stacked one per row under the form",
179
179
  " { icon?: '✨', title: 'Fast', body?: 'Sub-second responses' }",
180
180
  " ],",
181
181
  " showCount?: boolean, // renders '{N} on the waitlist' pill (hides if <3 signups)",
@@ -427,6 +427,46 @@ export const publishContactFlow = {
427
427
  inputSchema: z.object({ appIdOrSlug: z.string().min(1) }),
428
428
  handler: (input, cfg) => apiFetch(cfg, "POST", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/contact/publish`),
429
429
  };
430
+ // ─── Link page (link-in-bio) ────────────────────────────────────────────────
431
+ export const getLinkPageFlow = {
432
+ name: "get_link_page_flow",
433
+ description: "Read the published and draft link-page configs for an app. A link page is a shareable 'link-in-bio' page (app logo + title/description, a row of icon links, and a list of labeled links) at appmate.cloud/p/{appSlug} — drop it in an Instagram/TikTok bio.",
434
+ inputSchema: z.object({ appIdOrSlug: z.string().min(1) }),
435
+ handler: (input, cfg) => apiFetch(cfg, "GET", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/link-page`),
436
+ };
437
+ export const updateLinkPageDraft = {
438
+ name: "update_link_page_draft",
439
+ description: [
440
+ "Replace the draft link-page config. Body MUST be a full link_page config object (type: 'link_page').",
441
+ "",
442
+ "Shape:",
443
+ " {",
444
+ " type: 'link_page',",
445
+ " header: { title, description? }, // logo comes from the app record",
446
+ " iconLinks: [ // top row of compact icon buttons (<=6)",
447
+ " { icon: 'ri:instagram', label: 'Instagram', url: 'https://…' } // icon = a brand 'ri:<key>' (instagram/tiktok/x/youtube/…), a lucide name (e.g. 'Globe'), or an emoji",
448
+ " ],",
449
+ " links: [ // main list of labeled links (<=25)",
450
+ " { label: 'Download on the App Store', sublabel?: 'iPhone & iPad', url: 'https://…' }",
451
+ " ],",
452
+ " legal?: string, // small print at the bottom",
453
+ " hero?: { // visual treatment",
454
+ " theme?: 'minimal' | 'gradient' | 'dark' | 'side_by_side',",
455
+ " eyebrow?, accentColor?, titleFont?",
456
+ " }",
457
+ " }",
458
+ "",
459
+ "No submissions — a link page is a published, versioned page. Publish with publish_link_page_flow.",
460
+ ].join("\n"),
461
+ inputSchema: updateDraftInput,
462
+ handler: (input, cfg) => apiFetch(cfg, "PUT", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/link-page`, input.config),
463
+ };
464
+ export const publishLinkPageFlow = {
465
+ name: "publish_link_page_flow",
466
+ description: "Promote the draft link-page config to the live published version. Visitors at appmate.cloud/p/{appSlug} see the new version immediately.",
467
+ inputSchema: z.object({ appIdOrSlug: z.string().min(1) }),
468
+ handler: (input, cfg) => apiFetch(cfg, "POST", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/link-page/publish`),
469
+ };
430
470
  export const listContactSubmissions = {
431
471
  name: "list_contact_submissions",
432
472
  description: "Paginated list of contact submissions for an app. Each row: { id, name, email, message, source, country, createdAt }. limit max 200, default 50; pass nextCursor back for next page.",
@@ -599,20 +639,188 @@ export const exportReferralsCsv = {
599
639
  return { csv };
600
640
  },
601
641
  };
642
+ // ─── Wishlist flow (feature-request board) ──────────────────────────────────
643
+ //
644
+ // Unlike every other flow (write-only), the wishlist is an INTERACTIVE board:
645
+ // ideas are read back, upvoted, commented on, and moderated. Beyond the usual
646
+ // get/update/publish trio it exposes moderation tools (set status, delete idea,
647
+ // reply, delete comment) — the first MUTATING admin tools in the set.
648
+ export const getWishlistFlow = {
649
+ name: "get_wishlist_flow",
650
+ description: "Read the published and draft wishlist (feature-request board) configs for an app. The board lives at appmate.cloud/wishlist/{appSlug}; visitors submit ideas, upvote, and comment, and the owner moderates status.",
651
+ inputSchema: z.object({ appIdOrSlug: z.string().min(1) }),
652
+ handler: (input, cfg) => apiFetch(cfg, "GET", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/wishlist`),
653
+ };
654
+ export const updateWishlistDraft = {
655
+ name: "update_wishlist_draft",
656
+ description: [
657
+ "Replace the draft wishlist config. Body MUST be a full wishlist config object (type: 'wishlist').",
658
+ "",
659
+ "Shape:",
660
+ " {",
661
+ " type: 'wishlist',",
662
+ " intro: { title, subtitle, submitLabel, legal? },",
663
+ " identity?: { // who can participate + vote dedup",
664
+ " mode: 'anonymous' | 'email', // default 'anonymous' (dedup by cookie+IP)",
665
+ " requireEmailToSubmit?, requireEmailToVote?, requireEmailToComment? // email mode only",
666
+ " },",
667
+ " categories?: [ // OPTIONAL tags (0–12)",
668
+ " { id: 'feature', label: 'New feature', emoji?: '✨', hint?: '…' }",
669
+ " ],",
670
+ " statusLabels?: { // rename lifecycle stages on the board",
671
+ " open?, planned?, in_progress?, done?, declined?",
672
+ " },",
673
+ " submitForm?: { titlePlaceholder?, bodyPlaceholder? },",
674
+ " moderation?: { autoApprove?: false }, // false (default) = new ideas start pending/hidden",
675
+ " success: { title, body, ctaLabel?, ctaUrl? },",
676
+ " hero?: { theme?, eyebrow?, accentColor?, titleFont? }",
677
+ " }",
678
+ "",
679
+ "Server returns { ok:true, warnings: [] }.",
680
+ ].join("\n"),
681
+ inputSchema: updateDraftInput,
682
+ handler: (input, cfg) => apiFetch(cfg, "PUT", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/wishlist`, input.config),
683
+ };
684
+ export const publishWishlistFlow = {
685
+ name: "publish_wishlist_flow",
686
+ description: "Promote the draft wishlist config to the live published version. Visitors at appmate.cloud/wishlist/{appSlug} see the new version immediately.",
687
+ inputSchema: z.object({ appIdOrSlug: z.string().min(1) }),
688
+ handler: (input, cfg) => apiFetch(cfg, "POST", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/wishlist/publish`),
689
+ };
690
+ export const listWishlistIdeas = {
691
+ name: "list_wishlist_ideas",
692
+ description: "Paginated list of wishlist ideas for an app — ALL statuses (including pending review). Each row: { id, title, body, category, status, voteCount, commentCount, pinned, author, email, source, country, createdAt }. Optional status ('pending'|'open'|'planned'|'in_progress'|'done'|'declined') + category filters; sort 'votes' (default 'new'). limit max 200, default 50; pass nextCursor back for the next page.",
693
+ inputSchema: z.object({
694
+ appIdOrSlug: z.string().min(1),
695
+ status: z.string().optional(),
696
+ category: z.string().optional(),
697
+ sort: z.enum(["votes", "new"]).optional(),
698
+ limit: z.number().int().min(1).max(200).optional(),
699
+ cursor: z.string().optional(),
700
+ }),
701
+ handler: (input, cfg) => {
702
+ const qs = new URLSearchParams();
703
+ if (input.status)
704
+ qs.set("status", input.status);
705
+ if (input.category)
706
+ qs.set("category", input.category);
707
+ if (input.sort)
708
+ qs.set("sort", input.sort);
709
+ if (input.limit !== undefined)
710
+ qs.set("limit", String(input.limit));
711
+ if (input.cursor)
712
+ qs.set("cursor", input.cursor);
713
+ const tail = qs.toString() ? `?${qs.toString()}` : "";
714
+ return apiFetch(cfg, "GET", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/wishlist/ideas${tail}`);
715
+ },
716
+ };
717
+ export const createWishlistIdea = {
718
+ name: "create_wishlist_idea",
719
+ description: "Seed an idea onto the board as the app owner (so it isn't empty for early visitors — they can then upvote/comment). Defaults to status 'open' (visible). Use a category id from the flow config when set.",
720
+ inputSchema: z.object({
721
+ appIdOrSlug: z.string().min(1),
722
+ title: z.string().min(1).max(120),
723
+ body: z.string().max(4000).optional(),
724
+ category: z.string().optional(),
725
+ status: z
726
+ .enum(["pending", "open", "planned", "in_progress", "done", "declined"])
727
+ .optional(),
728
+ }),
729
+ handler: (input, cfg) => apiFetch(cfg, "POST", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/wishlist/ideas`, {
730
+ title: input.title,
731
+ body: input.body,
732
+ category: input.category,
733
+ status: input.status,
734
+ }),
735
+ };
736
+ export const setWishlistIdeaStatus = {
737
+ name: "set_wishlist_idea_status",
738
+ description: "Moderate an idea: set its status and/or pin it. 'pending' hides it from the public board; 'open' approves it. Statuses: pending | open | planned | in_progress | done | declined.",
739
+ inputSchema: z.object({
740
+ appIdOrSlug: z.string().min(1),
741
+ ideaId: z.string().min(1),
742
+ status: z.enum(["pending", "open", "planned", "in_progress", "done", "declined"]),
743
+ pinned: z.boolean().optional(),
744
+ }),
745
+ handler: (input, cfg) => apiFetch(cfg, "PATCH", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/wishlist/ideas/${encodeURIComponent(input.ideaId)}`, { status: input.status, ...(input.pinned !== undefined ? { pinned: input.pinned } : {}) }),
746
+ };
747
+ export const deleteWishlistIdea = {
748
+ name: "delete_wishlist_idea",
749
+ description: "Permanently delete an idea AND all of its votes and comments. IRREVERSIBLE — there is no undo. Prefer set_wishlist_idea_status with 'declined' to reject an idea while keeping it (and the decision) visible.",
750
+ inputSchema: z.object({
751
+ appIdOrSlug: z.string().min(1),
752
+ ideaId: z.string().min(1),
753
+ }),
754
+ handler: (input, cfg) => apiFetch(cfg, "DELETE", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/wishlist/ideas/${encodeURIComponent(input.ideaId)}`),
755
+ };
756
+ export const listWishlistComments = {
757
+ name: "list_wishlist_comments",
758
+ description: "Paginated comments on an idea (oldest-first, excludes deleted). Each row: { id, body, author, isOwner, isOfficial, createdAt }. limit max 200, default 50; pass nextCursor back for more.",
759
+ inputSchema: z.object({
760
+ appIdOrSlug: z.string().min(1),
761
+ ideaId: z.string().min(1),
762
+ limit: z.number().int().min(1).max(200).optional(),
763
+ cursor: z.string().optional(),
764
+ }),
765
+ handler: (input, cfg) => {
766
+ const qs = new URLSearchParams();
767
+ if (input.limit !== undefined)
768
+ qs.set("limit", String(input.limit));
769
+ if (input.cursor)
770
+ qs.set("cursor", input.cursor);
771
+ const tail = qs.toString() ? `?${qs.toString()}` : "";
772
+ return apiFetch(cfg, "GET", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/wishlist/ideas/${encodeURIComponent(input.ideaId)}/comments${tail}`);
773
+ },
774
+ };
775
+ export const postWishlistComment = {
776
+ name: "post_wishlist_comment",
777
+ description: "Post an OWNER reply on an idea (shown with a Team badge). Set official:true to mark it an authoritative response. Use this to answer or expand on a user's idea.",
778
+ inputSchema: z.object({
779
+ appIdOrSlug: z.string().min(1),
780
+ ideaId: z.string().min(1),
781
+ body: z.string().min(1).max(2000),
782
+ official: z.boolean().optional(),
783
+ }),
784
+ handler: (input, cfg) => apiFetch(cfg, "POST", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/wishlist/ideas/${encodeURIComponent(input.ideaId)}/comments`, { body: input.body, official: input.official ?? false }),
785
+ };
786
+ export const deleteWishlistComment = {
787
+ name: "delete_wishlist_comment",
788
+ description: "Soft-delete a comment (hidden from the public board, thread continuity preserved). Use for moderation.",
789
+ inputSchema: z.object({
790
+ appIdOrSlug: z.string().min(1),
791
+ commentId: z.string().min(1),
792
+ }),
793
+ handler: (input, cfg) => apiFetch(cfg, "DELETE", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/wishlist/comments/${encodeURIComponent(input.commentId)}`),
794
+ };
795
+ export const exportWishlistCsv = {
796
+ name: "export_wishlist_csv",
797
+ description: "Return all wishlist ideas for an app as a CSV string (one row per idea, every status: created_at, status, title, votes, comments, category, email, author, country, body).",
798
+ inputSchema: z.object({ appIdOrSlug: z.string().min(1) }),
799
+ handler: async (input, cfg) => {
800
+ const csv = await apiFetchText(cfg, "GET", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/wishlist/ideas.csv`);
801
+ return { csv };
802
+ },
803
+ };
602
804
  // Registered alphabetically so `list_tools` reads predictably.
603
805
  export const ALL_TOOLS = [
604
806
  createApp,
807
+ createWishlistIdea,
808
+ deleteWishlistComment,
809
+ deleteWishlistIdea,
605
810
  exportOnboardingCsv,
606
811
  exportReferralsCsv,
607
812
  exportWaitlistCsv,
813
+ exportWishlistCsv,
608
814
  getApp,
609
815
  getCancelFlow,
610
816
  getContactFlow,
611
817
  getFeedbackFlow,
818
+ getLinkPageFlow,
612
819
  getOnboardingFlow,
613
820
  getReferralFlow,
614
821
  getReportFlow,
615
822
  getWaitlistFlow,
823
+ getWishlistFlow,
616
824
  listApps,
617
825
  listContactSubmissions,
618
826
  listFeedbackSubmissions,
@@ -620,18 +828,26 @@ export const ALL_TOOLS = [
620
828
  listReferrals,
621
829
  listReportSubmissions,
622
830
  listWaitlistSignups,
831
+ listWishlistComments,
832
+ listWishlistIdeas,
833
+ postWishlistComment,
623
834
  publishCancelFlow,
624
835
  publishContactFlow,
625
836
  publishFeedbackFlow,
837
+ publishLinkPageFlow,
626
838
  publishOnboardingFlow,
627
839
  publishReferralFlow,
628
840
  publishReportFlow,
629
841
  publishWaitlistFlow,
842
+ publishWishlistFlow,
843
+ setWishlistIdeaStatus,
630
844
  updateCancelDraft,
631
845
  updateContactDraft,
632
846
  updateFeedbackDraft,
847
+ updateLinkPageDraft,
633
848
  updateOnboardingDraft,
634
849
  updateReferralDraft,
635
850
  updateReportDraft,
636
851
  updateWaitlistDraft,
852
+ updateWishlistDraft,
637
853
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fil-technology/appmate-mcp",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Model Context Protocol server for AppMate — lets Claude / Cursor / Codex drive your retention flows via API tokens.",
5
5
  "type": "module",
6
6
  "bin": {