@reynandaptr/replyto-mcp 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,6 +23,7 @@ Instagram automations from any MCP-capable AI client.
23
23
 
24
24
  ## Tools
25
25
 
26
- Products: list, get, create, update, delete, replace links.
27
- Automations: list, get, create, update, toggle, delete, preview conflicts, list runs.
26
+ Products: list, get, update, delete, replace links.
27
+ Automations: list, get, update, toggle, delete, preview conflicts, list runs.
28
28
  Lookups: stores, Instagram accounts, Instagram media.
29
+ Analytics: aggregate metrics across bio, stores, products, short links, automations; account overview.
package/dist/client.js CHANGED
@@ -8,6 +8,7 @@ export const err = (text) => ({ content: [{ type: "text", text }], isError: true
8
8
  const HINTS = {
9
9
  unauthorized: "Check that REPLYTO_API_KEY is a valid, non-revoked Replyto API key.",
10
10
  plan_upgrade_required: "API access requires a paid Replyto plan — upgrade in the Replyto dashboard.",
11
+ plan_required: "Analytics requires a plan with full analytics — upgrade in the Replyto dashboard.",
11
12
  email_unverified: "Verify your email address in the Replyto dashboard first.",
12
13
  };
13
14
  export class ReplytoClient {
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { DEFAULT_BASE_URL } from "./config.js";
8
8
  import { productTools } from "./tools/products.js";
9
9
  import { automationTools } from "./tools/automations.js";
10
10
  import { lookupTools } from "./tools/lookups.js";
11
+ import { analyticsTools } from "./tools/analytics.js";
11
12
  const apiKey = process.env.REPLYTO_API_KEY;
12
13
  if (!apiKey) {
13
14
  console.error("REPLYTO_API_KEY is not set.\n" +
@@ -15,8 +16,8 @@ if (!apiKey) {
15
16
  process.exit(1);
16
17
  }
17
18
  const client = new ReplytoClient(process.env.REPLYTO_URL ?? DEFAULT_BASE_URL, apiKey);
18
- const server = new McpServer({ name: "replyto", version: "0.1.0" });
19
- for (const def of [...productTools, ...automationTools, ...lookupTools]) {
19
+ const server = new McpServer({ name: "replyto", version: "0.2.0" });
20
+ for (const def of [...productTools, ...automationTools, ...lookupTools, ...analyticsTools]) {
20
21
  server.registerTool(def.name, { description: def.description, inputSchema: def.inputSchema, annotations: def.annotations }, (args) => def.handler(client, args ?? {}));
21
22
  }
22
23
  try {
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ const dims = ["time", "surface", "target", "platform", "source", "device", "kind"];
3
+ export const analyticsTools = [
4
+ {
5
+ name: "aggregate_analytics",
6
+ description: "Aggregate event counts across the user's surfaces. metric=views (bio/store/product page views), " +
7
+ "clicks (bio_link/bio_social/product/short_link clicks), or automation_events (rule firings). " +
8
+ "group_by up to TWO of: time (bucket start; hourly for range=24h, daily 7d, weekly 30d, monthly 90d), " +
9
+ "surface, target (rows include a human label), platform (clicks), source (clicks; 'organic' = no campaign), " +
10
+ "device (views/clicks), kind (automation_events: reply_sent/dm_sent/dm_seen/error). " +
11
+ "Filters: surface, store_id, product_id (views/clicks), source (clicks), ig_account (automation_events). " +
12
+ "Cross-surface comparison: group_by=surface. Ranking: group_by=target. Time series: group_by=time. " +
13
+ "rows are capped at 500 (truncated flag set); total is the ungrouped count.",
14
+ inputSchema: {
15
+ metric: z.enum(["views", "clicks", "automation_events"]),
16
+ range: z.enum(["24h", "7d", "30d", "90d"]).optional(),
17
+ group_by: z.array(z.enum(dims)).max(2).optional(),
18
+ surface: z.string().optional().describe("views: bio|store|product; clicks: bio_link|bio_social|product|short_link"),
19
+ store_id: z.string().uuid().optional(),
20
+ product_id: z.string().uuid().optional(),
21
+ ig_account: z.string().optional().describe("Instagram handle (automation_events only)"),
22
+ source: z.string().optional().describe("clicks only, e.g. dm or organic"),
23
+ },
24
+ annotations: { readOnlyHint: true },
25
+ handler: (c, { group_by, ...rest }) => c.call("GET", "/analytics/aggregate", {
26
+ query: {
27
+ ...rest,
28
+ ...(Array.isArray(group_by) && group_by.length ? { group_by: group_by.join(",") } : {}),
29
+ },
30
+ }),
31
+ },
32
+ {
33
+ name: "get_overview",
34
+ description: "Account overview: replies/DMs sent with period-over-period deltas, DM open rate, automation counts, " +
35
+ "connected accounts, product counts, storefront views and product-click CTR (paid plans).",
36
+ inputSchema: {
37
+ range: z.enum(["7d", "30d", "90d"]).optional().describe("Defaults to 7d"),
38
+ },
39
+ annotations: { readOnlyHint: true },
40
+ handler: (c, a) => c.call("GET", "/overview", { query: a }),
41
+ },
42
+ ];
@@ -40,13 +40,6 @@ export const automationTools = [
40
40
  annotations: { readOnlyHint: true },
41
41
  handler: (c, { id }) => c.call("GET", `/automations/${id}`),
42
42
  },
43
- {
44
- name: "create_automation",
45
- description: "Create an Instagram auto-reply/DM rule. On a 409 the response lists conflicting rules — " +
46
- "adjust keyword/scope/accounts or ask the user. Consider preview_automation_conflicts first.",
47
- inputSchema: ruleFields,
48
- handler: (c, a) => c.call("POST", "/automations", { body: a }),
49
- },
50
43
  {
51
44
  name: "update_automation",
52
45
  description: "Update rule fields by id. Only send the fields to change.",
@@ -71,7 +64,7 @@ export const automationTools = [
71
64
  },
72
65
  {
73
66
  name: "preview_automation_conflicts",
74
- description: "Dry-run conflict check for a prospective rule — returns conflicting existing rules without creating anything. " +
67
+ description: "Dry-run conflict check for a prospective rule configuration — returns conflicting existing rules without changing anything. " +
75
68
  "Pass exclude_id when checking an edit to an existing rule.",
76
69
  inputSchema: {
77
70
  trigger: ruleFields.trigger,
@@ -8,6 +8,7 @@ const linkInput = {
8
8
  price: z.number().int().optional().describe("Current price in integer rupiah (no decimals)"),
9
9
  original: z.number().int().optional().describe("Pre-discount price in integer rupiah; omit if no discount"),
10
10
  label: z.string().optional().describe("Optional badge label shown on the button, e.g. 'Official Store'"),
11
+ display_name: z.string().optional().describe("Display name shown instead of the platform label, e.g. 'Toko Utama Jakarta'"),
11
12
  };
12
13
  const productFields = {
13
14
  slug: z.string().describe("URL slug for the public product page (/p/<slug>). Lowercase, hyphenated."),
@@ -44,13 +45,6 @@ export const productTools = [
44
45
  annotations: { readOnlyHint: true },
45
46
  handler: (c, { id }) => c.call("GET", `/products/${id}`),
46
47
  },
47
- {
48
- name: "create_product",
49
- description: "Create a product. mode=platforms needs links[] (marketplace buttons); mode=redirect needs redirect_url. " +
50
- "The API auto-creates short links and returns their click URLs.",
51
- inputSchema: productFields,
52
- handler: (c, a) => c.call("POST", "/products", { body: a }),
53
- },
54
48
  {
55
49
  name: "update_product",
56
50
  description: "Update product fields by id. Only send the fields to change. Links are managed via replace_product_links.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reynandaptr/replyto-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "MCP server for Replyto — manage your products and Instagram automations from any AI client",
5
5
  "license": "MIT",
6
6
  "type": "module",