@fil-technology/appmate-mcp 0.5.0 → 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.
- package/README.md +8 -1
- package/dist/tools.js +221 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,8 +88,15 @@ staging or self-hosted instances.
|
|
|
88
88
|
| `get_referral_flow` | Read published + draft referral program config. |
|
|
89
89
|
| `update_referral_draft` | Replace the referral draft (rewards, share text, cap, landing). |
|
|
90
90
|
| `publish_referral_flow` | Promote the referral draft live. |
|
|
91
|
-
| `list_referrals` | Paginated referral graph (status, referee, reward flags). |
|
|
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}). |
|
|
96
|
+
|
|
97
|
+
Each referrer gets a unique link **and** a short, human-readable code (e.g.
|
|
98
|
+
`K7Q4-R9XP`); a friend redeems by tapping the link or **typing the code** (no
|
|
99
|
+
clipboard needed). `source` on each referral row records which path was used.
|
|
93
100
|
|
|
94
101
|
Tools that accept an app reference (`get_app`, `update_cancel_draft`,
|
|
95
102
|
etc.) accept either the cuid `id` or the human-readable `slug` — use
|
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)",
|
|
@@ -250,6 +250,7 @@ export const updateFeedbackDraft = {
|
|
|
250
250
|
"Shape:",
|
|
251
251
|
" {",
|
|
252
252
|
" type: 'feedback',",
|
|
253
|
+
" layout: 'steps' | 'single', // optional, default 'steps' (multi-step: rating+message, then email). 'single' = one screen.",
|
|
253
254
|
" intro: {",
|
|
254
255
|
" title, subtitle,",
|
|
255
256
|
" messagePlaceholder, // textarea placeholder",
|
|
@@ -320,6 +321,7 @@ export const updateReportDraft = {
|
|
|
320
321
|
"Shape:",
|
|
321
322
|
" {",
|
|
322
323
|
" type: 'report',",
|
|
324
|
+
" layout: 'steps' | 'single', // optional, default 'steps' (multi-step: category, then message+email). 'single' = one screen.",
|
|
323
325
|
" intro: {",
|
|
324
326
|
" title, subtitle,",
|
|
325
327
|
" messagePlaceholder, // textarea placeholder",
|
|
@@ -385,6 +387,7 @@ export const updateContactDraft = {
|
|
|
385
387
|
"Shape:",
|
|
386
388
|
" {",
|
|
387
389
|
" type: 'contact',",
|
|
390
|
+
" layout: 'steps' | 'single', // optional, default 'steps' (multi-step: message, then name+email). 'single' = one screen.",
|
|
388
391
|
" intro: {",
|
|
389
392
|
" title, subtitle,",
|
|
390
393
|
" submitLabel,",
|
|
@@ -424,6 +427,46 @@ export const publishContactFlow = {
|
|
|
424
427
|
inputSchema: z.object({ appIdOrSlug: z.string().min(1) }),
|
|
425
428
|
handler: (input, cfg) => apiFetch(cfg, "POST", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/contact/publish`),
|
|
426
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
|
+
};
|
|
427
470
|
export const listContactSubmissions = {
|
|
428
471
|
name: "list_contact_submissions",
|
|
429
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.",
|
|
@@ -542,6 +585,7 @@ export const updateReferralDraft = {
|
|
|
542
585
|
" landing: { // the invite page a friend sees at /r/{code}",
|
|
543
586
|
" eyebrow?, title, subtitle, ctaLabel,",
|
|
544
587
|
" appStoreUrl?, // REQUIRED before go-live — the install button target",
|
|
588
|
+
" fallback?: 'app_store' | 'website', // where a not-installed friend goes (default app_store; 'website' uses the app's websiteUrl)",
|
|
545
589
|
" legal?",
|
|
546
590
|
" },",
|
|
547
591
|
" share: { messageTemplate }, // the referrer's share text; the link is appended automatically",
|
|
@@ -595,20 +639,188 @@ export const exportReferralsCsv = {
|
|
|
595
639
|
return { csv };
|
|
596
640
|
},
|
|
597
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
|
+
};
|
|
598
804
|
// Registered alphabetically so `list_tools` reads predictably.
|
|
599
805
|
export const ALL_TOOLS = [
|
|
600
806
|
createApp,
|
|
807
|
+
createWishlistIdea,
|
|
808
|
+
deleteWishlistComment,
|
|
809
|
+
deleteWishlistIdea,
|
|
601
810
|
exportOnboardingCsv,
|
|
602
811
|
exportReferralsCsv,
|
|
603
812
|
exportWaitlistCsv,
|
|
813
|
+
exportWishlistCsv,
|
|
604
814
|
getApp,
|
|
605
815
|
getCancelFlow,
|
|
606
816
|
getContactFlow,
|
|
607
817
|
getFeedbackFlow,
|
|
818
|
+
getLinkPageFlow,
|
|
608
819
|
getOnboardingFlow,
|
|
609
820
|
getReferralFlow,
|
|
610
821
|
getReportFlow,
|
|
611
822
|
getWaitlistFlow,
|
|
823
|
+
getWishlistFlow,
|
|
612
824
|
listApps,
|
|
613
825
|
listContactSubmissions,
|
|
614
826
|
listFeedbackSubmissions,
|
|
@@ -616,18 +828,26 @@ export const ALL_TOOLS = [
|
|
|
616
828
|
listReferrals,
|
|
617
829
|
listReportSubmissions,
|
|
618
830
|
listWaitlistSignups,
|
|
831
|
+
listWishlistComments,
|
|
832
|
+
listWishlistIdeas,
|
|
833
|
+
postWishlistComment,
|
|
619
834
|
publishCancelFlow,
|
|
620
835
|
publishContactFlow,
|
|
621
836
|
publishFeedbackFlow,
|
|
837
|
+
publishLinkPageFlow,
|
|
622
838
|
publishOnboardingFlow,
|
|
623
839
|
publishReferralFlow,
|
|
624
840
|
publishReportFlow,
|
|
625
841
|
publishWaitlistFlow,
|
|
842
|
+
publishWishlistFlow,
|
|
843
|
+
setWishlistIdeaStatus,
|
|
626
844
|
updateCancelDraft,
|
|
627
845
|
updateContactDraft,
|
|
628
846
|
updateFeedbackDraft,
|
|
847
|
+
updateLinkPageDraft,
|
|
629
848
|
updateOnboardingDraft,
|
|
630
849
|
updateReferralDraft,
|
|
631
850
|
updateReportDraft,
|
|
632
851
|
updateWaitlistDraft,
|
|
852
|
+
updateWishlistDraft,
|
|
633
853
|
];
|
package/package.json
CHANGED