@fil-technology/appmate-mcp 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fil Technology
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # appmate-mcp
2
+
3
+ Model Context Protocol server for [AppMate](https://appmate.cloud) — lets
4
+ Claude Desktop, Claude Code, Cursor, Codex, or any other MCP-aware client
5
+ drive AppMate via typed tools. List apps, edit cancel flows, publish,
6
+ export waitlists — without leaving the chat.
7
+
8
+ ```bash
9
+ npx -y @fil-technology/appmate-mcp
10
+ ```
11
+
12
+ ## Setup (1 minute)
13
+
14
+ 1. Issue a token at <https://flow.appmate.cloud/admin/api-tokens>. Copy the
15
+ `amk_…` string — it's only shown once.
16
+ 2. Add the server to your MCP host's config (examples below).
17
+ 3. Restart the host and ask your agent: *"list my appmate apps"*.
18
+
19
+ ### Claude Desktop / Claude Code
20
+
21
+ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (on
22
+ macOS) or the equivalent on your platform:
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "appmate": {
28
+ "command": "npx",
29
+ "args": ["-y", "@fil-technology/appmate-mcp"],
30
+ "env": {
31
+ "APPMATE_TOKEN": "amk_…",
32
+ "APPMATE_API_URL": "https://flow.appmate.cloud"
33
+ }
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ ### Cursor / Codex (`.mcp.json` in your project)
40
+
41
+ ```json
42
+ {
43
+ "mcpServers": {
44
+ "appmate": {
45
+ "command": "npx",
46
+ "args": ["-y", "@fil-technology/appmate-mcp"],
47
+ "env": { "APPMATE_TOKEN": "amk_…" }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ `APPMATE_API_URL` defaults to `https://flow.appmate.cloud`. Override for
54
+ staging or self-hosted instances.
55
+
56
+ ## Tools
57
+
58
+ | Tool | Purpose |
59
+ | --- | --- |
60
+ | `list_apps` | List every app the token can see. |
61
+ | `get_app` | Fetch one app by id or slug. |
62
+ | `create_app` | Create a new app. |
63
+ | `get_cancel_flow` | Read published + draft cancel config. |
64
+ | `update_cancel_draft` | Replace the draft with new config JSON. |
65
+ | `publish_cancel_flow` | Promote the draft live. |
66
+ | `get_waitlist_flow` | Read published + draft waitlist config. |
67
+ | `update_waitlist_draft` | Replace the waitlist draft. |
68
+ | `publish_waitlist_flow` | Promote the waitlist draft live. |
69
+ | `list_waitlist_signups` | Paginated list (cursor + nextCursor). |
70
+ | `export_waitlist_csv` | Return the full waitlist as a CSV string. |
71
+
72
+ Tools that accept an app reference (`get_app`, `update_cancel_draft`,
73
+ etc.) accept either the cuid `id` or the human-readable `slug` — use
74
+ whichever you have. The full REST shape is documented at
75
+ <https://docs.appmate.cloud/api-reference>.
76
+
77
+ ## Example agent prompts
78
+
79
+ > *"Create an AppMate app called `Ledgr` with bundle id
80
+ > `com.acme.ledgr`, then publish a simple cancel flow that offers a 20%
81
+ > discount for the `too_expensive` reason."*
82
+
83
+ > *"Export the waitlist for `appmate-pro` as CSV and save it to
84
+ > `~/Downloads/waitlist.csv`."*
85
+
86
+ > *"Compare the published and draft cancel configs for `quakemate` and
87
+ > tell me what changed."*
88
+
89
+ ## Local development
90
+
91
+ ```bash
92
+ pnpm install
93
+ pnpm dev # tsx src/index.ts — talks MCP over stdio
94
+ pnpm build # emits dist/index.js
95
+ ```
96
+
97
+ ## Security
98
+
99
+ - Tokens are bcrypt-hashed server-side and only shown once on creation.
100
+ - Revoke from the dashboard the moment a token leaks.
101
+ - All calls go over TLS to `flow.appmate.cloud`. No data sits on disk in
102
+ the MCP process beyond what your MCP host logs.
103
+
104
+ ## License
105
+
106
+ MIT. See `LICENSE`.
@@ -0,0 +1,64 @@
1
+ // Thin fetch wrapper for the AppMate REST surface. Each tool builds the
2
+ // path + body and hands it off here. Centralizing the Bearer header + base
3
+ // URL + error parsing keeps each tool definition focused on its inputs
4
+ // and the response shape.
5
+ export class AppMateApiError extends Error {
6
+ status;
7
+ body;
8
+ constructor(status, body) {
9
+ super(`AppMate API error ${status}: ${typeof body === "object" && body && "error" in body
10
+ ? String(body.error)
11
+ : JSON.stringify(body)}`);
12
+ this.status = status;
13
+ this.body = body;
14
+ }
15
+ }
16
+ export async function apiFetch(cfg, method, path, body) {
17
+ const url = `${cfg.baseUrl.replace(/\/$/, "")}${path}`;
18
+ const headers = {
19
+ Authorization: `Bearer ${cfg.token}`,
20
+ Accept: "application/json",
21
+ };
22
+ if (body !== undefined) {
23
+ headers["Content-Type"] = "application/json";
24
+ }
25
+ const res = await fetch(url, {
26
+ method,
27
+ headers,
28
+ body: body === undefined ? undefined : JSON.stringify(body),
29
+ });
30
+ if (!res.ok) {
31
+ let payload;
32
+ try {
33
+ payload = await res.json();
34
+ }
35
+ catch {
36
+ payload = await res.text().catch(() => "");
37
+ }
38
+ throw new AppMateApiError(res.status, payload);
39
+ }
40
+ // Empty 204s are rare here but harmless to handle.
41
+ const text = await res.text();
42
+ if (!text)
43
+ return undefined;
44
+ return JSON.parse(text);
45
+ }
46
+ // Helper for CSV endpoints — returns the raw body string instead of JSON.
47
+ export async function apiFetchText(cfg, method, path) {
48
+ const url = `${cfg.baseUrl.replace(/\/$/, "")}${path}`;
49
+ const res = await fetch(url, {
50
+ method,
51
+ headers: { Authorization: `Bearer ${cfg.token}` },
52
+ });
53
+ if (!res.ok) {
54
+ let payload;
55
+ try {
56
+ payload = await res.json();
57
+ }
58
+ catch {
59
+ payload = await res.text().catch(() => "");
60
+ }
61
+ throw new AppMateApiError(res.status, payload);
62
+ }
63
+ return res.text();
64
+ }
package/dist/index.js ADDED
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { z } from "zod";
6
+ import { ALL_TOOLS } from "./tools.js";
7
+ import { AppMateApiError } from "./api-client.js";
8
+ // stdio MCP entry. Install: `npx -y @fil-technology/appmate-mcp`.
9
+ //
10
+ // Auth: APPMATE_TOKEN env var (or --token CLI flag). Base URL defaults to
11
+ // https://flow.appmate.cloud — override with APPMATE_API_URL for staging
12
+ // or self-hosted setups.
13
+ function parseCli() {
14
+ let token = process.env.APPMATE_TOKEN ?? null;
15
+ let apiUrl = process.env.APPMATE_API_URL ?? "https://flow.appmate.cloud";
16
+ const argv = process.argv.slice(2);
17
+ for (let i = 0; i < argv.length; i++) {
18
+ const a = argv[i];
19
+ if (a === "--token" && argv[i + 1]) {
20
+ token = argv[i + 1] ?? null;
21
+ i++;
22
+ }
23
+ else if (a?.startsWith("--token=")) {
24
+ token = a.slice("--token=".length);
25
+ }
26
+ else if (a === "--api-url" && argv[i + 1]) {
27
+ apiUrl = argv[i + 1] ?? apiUrl;
28
+ i++;
29
+ }
30
+ else if (a?.startsWith("--api-url=")) {
31
+ apiUrl = a.slice("--api-url=".length);
32
+ }
33
+ }
34
+ return { token, apiUrl };
35
+ }
36
+ async function main() {
37
+ const { token, apiUrl } = parseCli();
38
+ if (!token) {
39
+ // Emit on stderr — stdout is reserved for the MCP wire protocol.
40
+ process.stderr.write([
41
+ "appmate-mcp: missing token. Set APPMATE_TOKEN or pass --token amk_...",
42
+ "",
43
+ "Issue a token at https://flow.appmate.cloud/admin/api-tokens.",
44
+ "",
45
+ ].join("\n"));
46
+ process.exit(1);
47
+ }
48
+ const cfg = { baseUrl: apiUrl, token };
49
+ const server = new Server({ name: "appmate-mcp", version: "0.1.0" }, { capabilities: { tools: {} } });
50
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
51
+ tools: ALL_TOOLS.map((t) => ({
52
+ name: t.name,
53
+ description: t.description,
54
+ inputSchema: zodToJsonSchema(t.inputSchema),
55
+ })),
56
+ }));
57
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
58
+ // Type-erase at the registration boundary: each tool has its own
59
+ // inputSchema/handler input type, but the union of handlers expects
60
+ // the *intersection* of input types, not the union. Cast to a loose
61
+ // shape — the per-tool zod schema is what actually validates input.
62
+ const tool = ALL_TOOLS.find((t) => t.name === req.params.name);
63
+ if (!tool) {
64
+ return {
65
+ isError: true,
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: `Unknown tool: ${req.params.name}`,
70
+ },
71
+ ],
72
+ };
73
+ }
74
+ try {
75
+ const parsed = tool.inputSchema.parse(req.params.arguments ?? {});
76
+ const result = await tool.handler(parsed, cfg);
77
+ return {
78
+ content: [
79
+ { type: "text", text: JSON.stringify(result, null, 2) },
80
+ ],
81
+ };
82
+ }
83
+ catch (err) {
84
+ const msg = err instanceof AppMateApiError
85
+ ? `AppMate API ${err.status}: ${JSON.stringify(err.body, null, 2)}`
86
+ : err instanceof Error
87
+ ? err.message
88
+ : String(err);
89
+ return {
90
+ isError: true,
91
+ content: [{ type: "text", text: msg }],
92
+ };
93
+ }
94
+ });
95
+ const transport = new StdioServerTransport();
96
+ await server.connect(transport);
97
+ }
98
+ function zodToJsonSchema(schema) {
99
+ if (schema instanceof z.ZodObject) {
100
+ const shape = schema.shape;
101
+ const properties = {};
102
+ const required = [];
103
+ for (const [k, v] of Object.entries(shape)) {
104
+ const inner = unwrapOptional(v);
105
+ properties[k] = zodToJsonSchema(inner.schema);
106
+ if (!inner.optional)
107
+ required.push(k);
108
+ }
109
+ const out = {
110
+ type: "object",
111
+ properties,
112
+ additionalProperties: false,
113
+ };
114
+ if (required.length)
115
+ out.required = required;
116
+ return out;
117
+ }
118
+ if (schema instanceof z.ZodString) {
119
+ return { type: "string" };
120
+ }
121
+ if (schema instanceof z.ZodNumber) {
122
+ return { type: "number" };
123
+ }
124
+ if (schema instanceof z.ZodBoolean) {
125
+ return { type: "boolean" };
126
+ }
127
+ if (schema instanceof z.ZodUnknown || schema instanceof z.ZodAny) {
128
+ return {};
129
+ }
130
+ return {};
131
+ }
132
+ function unwrapOptional(schema) {
133
+ if (schema instanceof z.ZodOptional) {
134
+ return { schema: schema._def.innerType, optional: true };
135
+ }
136
+ return { schema, optional: false };
137
+ }
138
+ main().catch((err) => {
139
+ process.stderr.write(`appmate-mcp: fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
140
+ process.exit(1);
141
+ });
package/dist/tools.js ADDED
@@ -0,0 +1,198 @@
1
+ import { z } from "zod";
2
+ import { apiFetch, apiFetchText } from "./api-client.js";
3
+ // ─── Apps ───────────────────────────────────────────────────────────────────
4
+ export const listApps = {
5
+ name: "list_apps",
6
+ description: "List every AppMate app the API token can see. Returns id, slug, name, bundleId, deepLinkScheme, logoUrl, timestamps.",
7
+ inputSchema: z.object({}),
8
+ handler: (_input, cfg) => apiFetch(cfg, "GET", "/api/v1/apps"),
9
+ };
10
+ export const getApp = {
11
+ name: "get_app",
12
+ description: "Fetch a single AppMate app by its cuid id or slug. Use list_apps first if you don't know either.",
13
+ inputSchema: z.object({
14
+ appIdOrSlug: z
15
+ .string()
16
+ .min(1)
17
+ .describe("Either the app's cuid id or its slug."),
18
+ }),
19
+ handler: (input, cfg) => apiFetch(cfg, "GET", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}`),
20
+ };
21
+ export const createApp = {
22
+ name: "create_app",
23
+ description: "Create a new AppMate app. The slug auto-derives from the name when omitted.",
24
+ inputSchema: z.object({
25
+ name: z.string().min(1).max(120),
26
+ slug: z
27
+ .string()
28
+ .min(1)
29
+ .max(80)
30
+ .regex(/^[a-z0-9-]+$/)
31
+ .optional(),
32
+ bundleId: z.string().max(200).optional(),
33
+ deepLinkScheme: z
34
+ .string()
35
+ .max(40)
36
+ .regex(/^[a-z][a-z0-9+.-]*$/)
37
+ .optional(),
38
+ logoUrl: z.string().url().optional(),
39
+ }),
40
+ handler: (input, cfg) => apiFetch(cfg, "POST", "/api/v1/apps", input),
41
+ };
42
+ // ─── Pre-cancel flow ────────────────────────────────────────────────────────
43
+ export const getCancelFlow = {
44
+ name: "get_cancel_flow",
45
+ description: "Read the published and draft cancel flow configs for an app.",
46
+ inputSchema: z.object({ appIdOrSlug: z.string().min(1) }),
47
+ handler: (input, cfg) => apiFetch(cfg, "GET", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/cancel`),
48
+ };
49
+ // Config body is `z.unknown()` so we hand the raw JSON to the server,
50
+ // which has the canonical Zod schema. The server returns 422 with paths
51
+ // on validation errors AND a `warnings` array on success for soft
52
+ // mismatches (e.g. showThanksScreen + a "Contact support" label).
53
+ export const updateCancelDraft = {
54
+ name: "update_cancel_draft",
55
+ description: [
56
+ "Replace the draft cancel flow config. Body MUST be a full cancel config object (type: 'cancel').",
57
+ "",
58
+ "Required shape (paste-and-fill):",
59
+ " {",
60
+ " type: 'cancel',",
61
+ " intro: { title, subtitle, primaryButton, secondaryButton },",
62
+ " reasonScreen: {",
63
+ " title, subtitle,",
64
+ " reasons: [ { id: snake_case, label, iconName? (lucide name), emoji? (one char) } ],",
65
+ " autoAdvance?: boolean,",
66
+ " selectMode?: 'single' (default) | 'multi' // multi skips response screens",
67
+ " },",
68
+ " responses: {",
69
+ " <reasonId>: {",
70
+ " title, body,",
71
+ " primaryButton: { label, action, ...actionParams },",
72
+ " secondaryButton?: { label, action, ...actionParams },",
73
+ " showThanksScreen?: boolean // true = no deep link, lands on thanks",
74
+ " }",
75
+ " },",
76
+ " showBackButton?: boolean (default true)",
77
+ " }",
78
+ "",
79
+ "Action types and their params:",
80
+ " return_to_app close flow, back to app",
81
+ " manage_subscription open Apple subs UI (escape hatch)",
82
+ " open_offer { offerId: string } iOS app applies a StoreKit promo",
83
+ " open_premium { paywallId?: string } iOS paywall (optional variant id)",
84
+ " open_support { supportTopic?, message? } iOS support inbox",
85
+ " open_feature { featureId: string } deep-link an in-app screen",
86
+ " external_url { url: https://… } open URL in browser",
87
+ " none record click, do nothing",
88
+ "",
89
+ "CRITICAL gotcha — showThanksScreen suppresses the deep link.",
90
+ " Set showThanksScreen:true ONLY when the click itself IS the signal you want.",
91
+ " Label the button feedback-shaped: 'Send feedback', 'Tell us why'.",
92
+ " DO NOT pair showThanksScreen:true with destination-shaped labels like",
93
+ " 'Contact support', 'Claim 20% off', 'Open tutorial' — the user taps,",
94
+ " lands on a generic thanks screen, and the promise the label made silently breaks.",
95
+ "",
96
+ "The server returns { ok:true, warnings: [...] } on success — ALWAYS check warnings",
97
+ "and re-PUT a corrected config before publishing if any are returned.",
98
+ "Common warning codes:",
99
+ " thanks_screen_blocks_navigation, missing_response, responses_unused_in_multi_mode,",
100
+ " placeholder_offer_id, placeholder_feature_id, placeholder_external_url",
101
+ "",
102
+ "See https://docs.appmate.cloud/ai-agents for the full do/don't guide and examples.",
103
+ ].join("\n"),
104
+ inputSchema: z.object({
105
+ appIdOrSlug: z.string().min(1),
106
+ config: z.unknown(),
107
+ }),
108
+ handler: (input, cfg) => apiFetch(cfg, "PUT", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/cancel`, input.config),
109
+ };
110
+ export const publishCancelFlow = {
111
+ name: "publish_cancel_flow",
112
+ description: "Promote the draft cancel config to the live published version. The live cancel URL flips on success. ALWAYS review the warnings array returned from the most recent update_cancel_draft call and fix any reported issues BEFORE publishing — publishing locks the broken flow in front of real users.",
113
+ inputSchema: z.object({ appIdOrSlug: z.string().min(1) }),
114
+ handler: (input, cfg) => apiFetch(cfg, "POST", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/cancel/publish`),
115
+ };
116
+ // ─── Waitlist flow ──────────────────────────────────────────────────────────
117
+ export const getWaitlistFlow = {
118
+ name: "get_waitlist_flow",
119
+ description: "Read the published and draft waitlist flow configs for an app.",
120
+ inputSchema: z.object({ appIdOrSlug: z.string().min(1) }),
121
+ handler: (input, cfg) => apiFetch(cfg, "GET", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/waitlist`),
122
+ };
123
+ export const updateWaitlistDraft = {
124
+ name: "update_waitlist_draft",
125
+ description: [
126
+ "Replace the draft waitlist config. Body MUST be a full waitlist config object (type: 'waitlist').",
127
+ "",
128
+ "Required shape:",
129
+ " {",
130
+ " type: 'waitlist',",
131
+ " intro: {",
132
+ " title, subtitle,",
133
+ " emailPlaceholder, submitLabel,",
134
+ " legal? (small print, optional)",
135
+ " },",
136
+ " success: {",
137
+ " title, body,",
138
+ " ctaLabel?, ctaUrl? // both-or-neither: ctaLabel without ctaUrl renders nothing",
139
+ " }",
140
+ " }",
141
+ "",
142
+ "Server returns { ok:true, warnings: [...] } — check warnings before publishing.",
143
+ "Common: partial_cta (label without url, or vice versa).",
144
+ ].join("\n"),
145
+ inputSchema: z.object({
146
+ appIdOrSlug: z.string().min(1),
147
+ config: z.unknown(),
148
+ }),
149
+ handler: (input, cfg) => apiFetch(cfg, "PUT", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/waitlist`, input.config),
150
+ };
151
+ export const publishWaitlistFlow = {
152
+ name: "publish_waitlist_flow",
153
+ description: "Promote the draft waitlist config to the live published version.",
154
+ inputSchema: z.object({ appIdOrSlug: z.string().min(1) }),
155
+ handler: (input, cfg) => apiFetch(cfg, "POST", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/flows/waitlist/publish`),
156
+ };
157
+ // ─── Waitlist signups ───────────────────────────────────────────────────────
158
+ export const listWaitlistSignups = {
159
+ name: "list_waitlist_signups",
160
+ description: "Paginated list of waitlist signups for an app. Returns up to `limit` rows (max 200, default 50) and a `nextCursor` to pass back for the next page.",
161
+ inputSchema: z.object({
162
+ appIdOrSlug: z.string().min(1),
163
+ limit: z.number().int().min(1).max(200).optional(),
164
+ cursor: z.string().optional(),
165
+ }),
166
+ handler: (input, cfg) => {
167
+ const qs = new URLSearchParams();
168
+ if (input.limit !== undefined)
169
+ qs.set("limit", String(input.limit));
170
+ if (input.cursor)
171
+ qs.set("cursor", input.cursor);
172
+ const tail = qs.toString() ? `?${qs.toString()}` : "";
173
+ return apiFetch(cfg, "GET", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/waitlist/signups${tail}`);
174
+ },
175
+ };
176
+ export const exportWaitlistCsv = {
177
+ name: "export_waitlist_csv",
178
+ description: "Return the full waitlist for an app as a CSV string (header row + one row per signup). Useful for hand-off to spreadsheet or mail merge.",
179
+ inputSchema: z.object({ appIdOrSlug: z.string().min(1) }),
180
+ handler: async (input, cfg) => {
181
+ const csv = await apiFetchText(cfg, "GET", `/api/v1/apps/${encodeURIComponent(input.appIdOrSlug)}/waitlist/signups.csv`);
182
+ return { csv };
183
+ },
184
+ };
185
+ // Registered alphabetically so `list_tools` reads predictably.
186
+ export const ALL_TOOLS = [
187
+ createApp,
188
+ exportWaitlistCsv,
189
+ getApp,
190
+ getCancelFlow,
191
+ getWaitlistFlow,
192
+ listApps,
193
+ listWaitlistSignups,
194
+ publishCancelFlow,
195
+ publishWaitlistFlow,
196
+ updateCancelDraft,
197
+ updateWaitlistDraft,
198
+ ];
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@fil-technology/appmate-mcp",
3
+ "version": "0.3.0",
4
+ "description": "Model Context Protocol server for AppMate — lets Claude / Cursor / Codex drive your retention flows via API tokens.",
5
+ "type": "module",
6
+ "bin": {
7
+ "appmate-mcp": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "engines": {
15
+ "node": ">=20"
16
+ },
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.json",
19
+ "dev": "tsx src/index.ts",
20
+ "prepare": "npm run build",
21
+ "prepublishOnly": "npm run build",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "dependencies": {
25
+ "@modelcontextprotocol/sdk": "^1.20.2",
26
+ "zod": "^3.25.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^22.10.0",
30
+ "tsx": "^4.19.0",
31
+ "typescript": "^5.6.0"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/fil-technology/appmate-mcp.git"
36
+ },
37
+ "homepage": "https://docs.appmate.cloud/api-reference",
38
+ "license": "MIT",
39
+ "keywords": [
40
+ "appmate",
41
+ "mcp",
42
+ "model-context-protocol",
43
+ "ai",
44
+ "claude",
45
+ "retention",
46
+ "subscription"
47
+ ]
48
+ }