@praise25/meta-mcp-server 0.1.6 → 0.1.8

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.
@@ -4,7 +4,7 @@ export declare const CHARACTER_LIMIT = 25000;
4
4
  export declare const DEFAULT_PAGE_LIMIT = 25;
5
5
  export declare const MAX_PAGE_LIMIT = 500;
6
6
  export declare const SERVER_NAME = "meta-business-manager-mcp-server";
7
- export declare const SERVER_VERSION = "0.1.4";
7
+ export declare const SERVER_VERSION = "0.1.8";
8
8
  export declare const META_ERROR_CODES: {
9
9
  readonly UNKNOWN: 1;
10
10
  readonly SERVICE_TEMPORARILY_UNAVAILABLE: 2;
package/dist/constants.js CHANGED
@@ -4,7 +4,7 @@ export const CHARACTER_LIMIT = 25_000;
4
4
  export const DEFAULT_PAGE_LIMIT = 25;
5
5
  export const MAX_PAGE_LIMIT = 500;
6
6
  export const SERVER_NAME = "meta-business-manager-mcp-server";
7
- export const SERVER_VERSION = "0.1.4";
7
+ export const SERVER_VERSION = "0.1.8";
8
8
  export const META_ERROR_CODES = {
9
9
  UNKNOWN: 1,
10
10
  SERVICE_TEMPORARILY_UNAVAILABLE: 2,
@@ -42,6 +42,49 @@ export function findPlaceholders(value, path = []) {
42
42
  function formatPath(p) {
43
43
  return p.length ? p.join(".") : "(root)";
44
44
  }
45
+ /**
46
+ * Per-field recovery hints: when the AI passes a placeholder for a specific
47
+ * field, tell it the *exact* discovery tool that produces the right ID. This
48
+ * turns "I need your IG account ID" (which the AI then asks the user) into
49
+ * "call meta_ig_list_accounts (no args) to discover IG accounts" (which the
50
+ * AI can do autonomously).
51
+ *
52
+ * Each entry returns a single-sentence next-step the AI should execute.
53
+ */
54
+ const FIELD_RECOVERY = {
55
+ business_id: "Call `meta_business_list` (no args) to discover Business Manager IDs. The configured business is `133767790806312` (Hodusoft) — use that directly if you already know.",
56
+ ad_account_id: "Call `meta_ads_list_accounts business_id=133767790806312` to discover ad accounts. Returns IDs in the `act_<digits>` form (e.g. `act_146517954996436`).",
57
+ page_id: "Call `meta_page_list` (no args, defaults to source='assigned') to discover Pages assigned to the configured token.",
58
+ ig_user_id: "Call `meta_ig_list_accounts` (no args) to discover Instagram Business accounts linked to your Pages. No parameters required.",
59
+ pixel_id: "Call `meta_pixel_list business_id=133767790806312` to discover pixels.",
60
+ catalog_id: "Call `meta_catalog_list business_id=133767790806312 scope='owned'` to discover catalogs.",
61
+ waba_id: "Call `meta_whatsapp_list_wabas business_id=133767790806312` to discover WhatsApp Business Accounts.",
62
+ media_id: "Call `meta_ig_list_media ig_user_id=<ig_id>` to discover media. Use `meta_ig_list_accounts` first if you don't have ig_id.",
63
+ creative_id: "Call `meta_ads_list_ads ad_account_id=act_<id>` to discover creatives. Each ad's `creative.id` is the value you want.",
64
+ campaign_id: "Call `meta_ads_list_campaigns ad_account_id=act_<id>` to discover campaign IDs.",
65
+ adset_id: "Call `meta_ads_list_adsets ad_account_id=act_<id>` to discover ad-set IDs.",
66
+ post_id: "Call `meta_page_list_posts page_id=<page_id>` to discover post IDs. Post IDs use the `{page_id}_{post_id}` format.",
67
+ object_id: "Pass an ad account (`act_<digits>`), campaign, ad-set, or ad ID. Use `meta_business_overview business_id=133767790806312` to discover all of these in one call.",
68
+ };
69
+ /** Build a numbered recovery action list keyed off the placeholder field names. */
70
+ function buildRecoveryActions(placeholders) {
71
+ const fields = new Set();
72
+ for (const p of placeholders) {
73
+ const top = p.path[0];
74
+ if (typeof top === "string")
75
+ fields.add(top);
76
+ }
77
+ const lines = [];
78
+ for (const f of fields) {
79
+ const recovery = FIELD_RECOVERY[f];
80
+ if (recovery)
81
+ lines.push(`• ${f}: ${recovery}`);
82
+ }
83
+ if (lines.length === 0) {
84
+ return "Replace the placeholder values with real IDs. If you don't know the IDs, call `meta_business_overview business_id=133767790806312` first to discover everything in one call.";
85
+ }
86
+ return `DO NOT ask the user for these IDs — the server can discover them for you. Replace each placeholder by first calling the corresponding discovery tool:\n${lines.join("\n")}\n\nShortcut: a single call to \`meta_business_overview business_id=133767790806312\` returns all Pages, IG accounts, ad accounts, pixels, catalogs, and WABAs at once. Prefer that when several placeholders appear together.`;
87
+ }
45
88
  /**
46
89
  * Defence-in-depth input validator. Wraps a Zod object schema and:
47
90
  * 1. Detects placeholder strings (e.g. literal "<YOUR_PAGE_ID>") with a
@@ -62,7 +105,7 @@ export function validateInput(schema, args) {
62
105
  .join("; ");
63
106
  return {
64
107
  ok: false,
65
- error: toolError(`Input contains placeholder values that were not substituted: ${summary}`, `Replace placeholders with real IDs / values before retrying. Examples for this server: business_id "133767790806312"; ad_account_id "act_146517954996436"; page_id "138368686823692". If you don't know the correct ID, call meta_business_list_assets first to discover available assets.`, { placeholders }),
108
+ error: toolError(`Input contains placeholder values that were not substituted: ${summary}`, buildRecoveryActions(placeholders), { placeholders, recovery_actions: buildRecoveryActions(placeholders) }),
66
109
  };
67
110
  }
68
111
  const parsed = schema.safeParse(args);
@@ -37,9 +37,9 @@ export declare const inputSchema: z.ZodObject<{
37
37
  sort: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
38
38
  }, "strict", z.ZodTypeAny, {
39
39
  fields: string[];
40
+ object_id: string;
40
41
  limit: number;
41
42
  auto_paginate: boolean;
42
- object_id: string;
43
43
  level: "account" | "campaign" | "adset" | "ad";
44
44
  date_preset: "today" | "yesterday" | "this_month" | "last_month" | "this_quarter" | "maximum" | "last_3d" | "last_7d" | "last_14d" | "last_28d" | "last_30d" | "last_90d" | "last_week_mon_sun" | "last_week_sun_sat" | "last_quarter" | "last_year" | "this_week_mon_today" | "this_week_sun_today" | "this_year";
45
45
  sort?: string[] | undefined;
@@ -9,9 +9,9 @@ export declare const inputSchema: z.ZodObject<{
9
9
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
10
10
  }, "strict", z.ZodTypeAny, {
11
11
  fields: string[];
12
+ business_id: string;
12
13
  limit: number;
13
14
  auto_paginate: boolean;
14
- business_id: string;
15
15
  scope: "owned" | "client" | "both";
16
16
  after?: string | undefined;
17
17
  }, {
@@ -11,22 +11,22 @@ export declare const inputSchema: z.ZodObject<{
11
11
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
12
12
  }, "strict", z.ZodTypeAny, {
13
13
  fields: string[];
14
+ ad_account_id: string;
14
15
  limit: number;
15
16
  auto_paginate: boolean;
16
- ad_account_id: string;
17
- after?: string | undefined;
18
- effective_status?: string[] | undefined;
19
17
  campaign_id?: string | undefined;
20
18
  adset_id?: string | undefined;
19
+ after?: string | undefined;
20
+ effective_status?: string[] | undefined;
21
21
  }, {
22
22
  ad_account_id: string;
23
23
  fields?: string[] | undefined;
24
+ campaign_id?: string | undefined;
25
+ adset_id?: string | undefined;
24
26
  limit?: number | undefined;
25
27
  after?: string | undefined;
26
28
  auto_paginate?: boolean | undefined;
27
29
  effective_status?: string[] | undefined;
28
- campaign_id?: string | undefined;
29
- adset_id?: string | undefined;
30
30
  }>;
31
31
  export type Input = z.infer<typeof inputSchema>;
32
32
  export declare const definition: {
@@ -10,20 +10,20 @@ export declare const inputSchema: z.ZodObject<{
10
10
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
11
11
  }, "strict", z.ZodTypeAny, {
12
12
  fields: string[];
13
+ ad_account_id: string;
13
14
  limit: number;
14
15
  auto_paginate: boolean;
15
- ad_account_id: string;
16
+ campaign_id?: string | undefined;
16
17
  after?: string | undefined;
17
18
  effective_status?: string[] | undefined;
18
- campaign_id?: string | undefined;
19
19
  }, {
20
20
  ad_account_id: string;
21
21
  fields?: string[] | undefined;
22
+ campaign_id?: string | undefined;
22
23
  limit?: number | undefined;
23
24
  after?: string | undefined;
24
25
  auto_paginate?: boolean | undefined;
25
26
  effective_status?: string[] | undefined;
26
- campaign_id?: string | undefined;
27
27
  }>;
28
28
  export type Input = z.infer<typeof inputSchema>;
29
29
  export declare const definition: {
@@ -9,9 +9,9 @@ export declare const inputSchema: z.ZodObject<{
9
9
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
10
10
  }, "strict", z.ZodTypeAny, {
11
11
  fields: string[];
12
+ ad_account_id: string;
12
13
  limit: number;
13
14
  auto_paginate: boolean;
14
- ad_account_id: string;
15
15
  after?: string | undefined;
16
16
  effective_status?: ("ACTIVE" | "PAUSED" | "DELETED" | "PENDING_REVIEW" | "DISAPPROVED" | "PREAPPROVED" | "PENDING_BILLING_INFO" | "CAMPAIGN_PAUSED" | "ARCHIVED" | "IN_PROCESS" | "WITH_ISSUES")[] | undefined;
17
17
  }, {
@@ -8,9 +8,9 @@ export declare const inputSchema: z.ZodObject<{
8
8
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
9
9
  }, "strict", z.ZodTypeAny, {
10
10
  fields: string[];
11
+ ad_account_id: string;
11
12
  limit: number;
12
13
  auto_paginate: boolean;
13
- ad_account_id: string;
14
14
  after?: string | undefined;
15
15
  }, {
16
16
  ad_account_id: string;
@@ -8,9 +8,9 @@ export declare const inputSchema: z.ZodObject<{
8
8
  business_id: z.ZodString;
9
9
  }, "strict", z.ZodTypeAny, {
10
10
  fields: string[];
11
+ business_id: string;
11
12
  limit: number;
12
13
  auto_paginate: boolean;
13
- business_id: string;
14
14
  after?: string | undefined;
15
15
  }, {
16
16
  business_id: string;
@@ -9,9 +9,9 @@ export declare const inputSchema: z.ZodObject<{
9
9
  filter: z.ZodOptional<z.ZodString>;
10
10
  }, "strict", z.ZodTypeAny, {
11
11
  fields: string[];
12
+ catalog_id: string;
12
13
  limit: number;
13
14
  auto_paginate: boolean;
14
- catalog_id: string;
15
15
  filter?: string | undefined;
16
16
  after?: string | undefined;
17
17
  }, {
@@ -9,9 +9,9 @@ export declare const inputSchema: z.ZodObject<{
9
9
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
10
10
  }, "strict", z.ZodTypeAny, {
11
11
  fields: string[];
12
+ business_id: string;
12
13
  limit: number;
13
14
  auto_paginate: boolean;
14
- business_id: string;
15
15
  scope: "owned" | "client" | "both";
16
16
  after?: string | undefined;
17
17
  }, {
@@ -7,8 +7,8 @@ export declare const inputSchema: z.ZodObject<{
7
7
  metric_type: z.ZodDefault<z.ZodEnum<["total_value", "time_series"]>>;
8
8
  timeframe: z.ZodDefault<z.ZodEnum<["last_14_days", "last_30_days", "last_90_days", "prev_month", "this_month", "this_week"]>>;
9
9
  }, "strict", z.ZodTypeAny, {
10
- metric: "follower_demographics" | "engaged_audience_demographics" | "reached_audience_demographics";
11
10
  ig_user_id: string;
11
+ metric: "follower_demographics" | "engaged_audience_demographics" | "reached_audience_demographics";
12
12
  breakdown: "age" | "gender" | "country" | "city" | "audience_city";
13
13
  metric_type: "total_value" | "time_series";
14
14
  timeframe: "this_month" | "last_14_days" | "last_30_days" | "last_90_days" | "prev_month" | "this_week";
@@ -4,8 +4,8 @@ export declare const inputSchema: z.ZodObject<{
4
4
  media_id: z.ZodString;
5
5
  metrics: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
6
6
  }, "strict", z.ZodTypeAny, {
7
- metrics: string[];
8
7
  media_id: string;
8
+ metrics: string[];
9
9
  }, {
10
10
  media_id: string;
11
11
  metrics?: string[] | undefined;
@@ -8,9 +8,9 @@ export declare const inputSchema: z.ZodObject<{
8
8
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
9
9
  }, "strict", z.ZodTypeAny, {
10
10
  fields: string[];
11
+ ig_user_id: string;
11
12
  limit: number;
12
13
  auto_paginate: boolean;
13
- ig_user_id: string;
14
14
  after?: string | undefined;
15
15
  }, {
16
16
  ig_user_id: string;
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import type { ToolContext } from "../../context.js";
3
3
  export declare const inputSchema: z.ZodObject<{
4
- business_id: z.ZodString;
4
+ business_id: z.ZodOptional<z.ZodString>;
5
5
  date_preset: z.ZodDefault<z.ZodEnum<["today", "yesterday", "this_month", "last_month", "this_quarter", "maximum", "last_3d", "last_7d", "last_14d", "last_28d", "last_30d", "last_90d", "last_week_mon_sun", "last_week_sun_sat", "last_quarter", "last_year", "this_week_mon_today", "this_week_sun_today", "this_year"]>>;
6
6
  include_pixels: z.ZodDefault<z.ZodBoolean>;
7
7
  include_catalogs: z.ZodDefault<z.ZodBoolean>;
@@ -9,15 +9,15 @@ export declare const inputSchema: z.ZodObject<{
9
9
  max_ad_accounts: z.ZodDefault<z.ZodNumber>;
10
10
  max_pages: z.ZodDefault<z.ZodNumber>;
11
11
  }, "strict", z.ZodTypeAny, {
12
- business_id: string;
13
12
  date_preset: "today" | "yesterday" | "this_month" | "last_month" | "this_quarter" | "maximum" | "last_3d" | "last_7d" | "last_14d" | "last_28d" | "last_30d" | "last_90d" | "last_week_mon_sun" | "last_week_sun_sat" | "last_quarter" | "last_year" | "this_week_mon_today" | "this_week_sun_today" | "this_year";
14
13
  include_pixels: boolean;
15
14
  include_catalogs: boolean;
16
15
  include_whatsapp: boolean;
17
16
  max_ad_accounts: number;
18
17
  max_pages: number;
18
+ business_id?: string | undefined;
19
19
  }, {
20
- business_id: string;
20
+ business_id?: string | undefined;
21
21
  date_preset?: "today" | "yesterday" | "this_month" | "last_month" | "this_quarter" | "maximum" | "last_3d" | "last_7d" | "last_14d" | "last_28d" | "last_30d" | "last_90d" | "last_week_mon_sun" | "last_week_sun_sat" | "last_quarter" | "last_year" | "this_week_mon_today" | "this_week_sun_today" | "this_year" | undefined;
22
22
  include_pixels?: boolean | undefined;
23
23
  include_catalogs?: boolean | undefined;
@@ -31,7 +31,7 @@ export declare const definition: {
31
31
  readonly title: "Eagle's-eye snapshot of a business";
32
32
  readonly description: "One-call consolidated read across the whole Meta surface for a business:\n\n- Identity (system user + token app)\n- Assigned Pages + each Page's high-level insights (impressions, engagement, fans) for the requested window\n- Instagram Business accounts linked to those Pages (followers, media_count)\n- Owned + client ad accounts with balance, spend cap, amount spent, and last-window insights (spend, impressions, clicks, CTR, CPC, reach)\n- Pixels with last_fired_time (if include_pixels)\n- Catalogs with product counts (if include_catalogs)\n- WhatsApp Business Accounts + phone numbers (if include_whatsapp)\n\nEach section has its own error isolation — one failing asset does not kill the report. Ideal as the opening call for any AI-driven marketing insights conversation.";
33
33
  readonly inputSchema: {
34
- business_id: z.ZodString;
34
+ business_id: z.ZodOptional<z.ZodString>;
35
35
  date_preset: z.ZodDefault<z.ZodEnum<["today", "yesterday", "this_month", "last_month", "this_quarter", "maximum", "last_3d", "last_7d", "last_14d", "last_28d", "last_30d", "last_90d", "last_week_mon_sun", "last_week_sun_sat", "last_quarter", "last_year", "this_week_mon_today", "this_week_sun_today", "this_year"]>>;
36
36
  include_pixels: z.ZodDefault<z.ZodBoolean>;
37
37
  include_catalogs: z.ZodDefault<z.ZodBoolean>;
@@ -5,7 +5,9 @@ import { jsonBlock, toolResult } from "../../helpers/format.js";
5
5
  import { datePresetSchema, metaIdSchema } from "../../helpers/schema.js";
6
6
  export const inputSchema = z
7
7
  .object({
8
- business_id: metaIdSchema.describe("Business Manager ID."),
8
+ business_id: metaIdSchema
9
+ .optional()
10
+ .describe("Business Manager ID. **Optional** — if omitted, the server auto-discovers via `META_ALLOWED_BUSINESS_IDS[0]` (if configured) or the first business returned by `/me/businesses`. Always prefer omitting this if you don't have the exact ID — do not guess a placeholder integer."),
9
11
  date_preset: datePresetSchema
10
12
  .default("last_30d")
11
13
  .describe("Date window used for ad account and page insights snapshots."),
@@ -41,8 +43,43 @@ function fail(err) {
41
43
  return { ok: false, error: e.message, hint: e.hint };
42
44
  }
43
45
  export async function handler(input, ctx) {
44
- assertAllowed("business", input.business_id, ctx.config);
45
46
  const started = Date.now();
47
+ // Auto-discover business_id if not provided. Prefer the configured allowlist
48
+ // (deployment intent), fall back to /me/businesses. This makes the tool
49
+ // safe to call with no args — defeats AI-hallucinated-id failure modes.
50
+ let businessId = input.business_id;
51
+ let businessIdSource = "input";
52
+ if (!businessId) {
53
+ const allowed = ctx.config.allowedBusinessIds;
54
+ if (allowed && allowed.size > 0) {
55
+ businessId = allowed.values().next().value;
56
+ businessIdSource = "allowlist";
57
+ }
58
+ else {
59
+ try {
60
+ const list = await ctx.graph.get({
61
+ path: "me/businesses",
62
+ params: { fields: "id,name", limit: 1 },
63
+ });
64
+ const first = list.data?.[0];
65
+ if (first?.id) {
66
+ businessId = first.id;
67
+ businessIdSource = "discovered";
68
+ }
69
+ }
70
+ catch {
71
+ /* fall through — businessId remains undefined and structured response will surface the gap */
72
+ }
73
+ }
74
+ }
75
+ if (!businessId) {
76
+ return toolResult({
77
+ error: "no_business_id",
78
+ hint: "Could not determine business_id. Configure META_ALLOWED_BUSINESS_IDS in the server's environment, or pass business_id explicitly. /me/businesses returned no results for the configured token (this is normal for system-user tokens — they don't list businesses via that edge).",
79
+ suggestion: "Pass business_id directly. To discover it, an operator should run `meta_business_list_assets business_id=<known>` once with a known ID, or look up the business in Business Settings → Business Info.",
80
+ }, "{}");
81
+ }
82
+ assertAllowed("business", businessId, ctx.config);
46
83
  const token = ctx.graph.get({
47
84
  path: "debug_token",
48
85
  params: { input_token: ctx.config.accessToken },
@@ -60,14 +97,14 @@ export async function handler(input, ctx) {
60
97
  .catch(fail);
61
98
  const ownedAds = ctx.graph
62
99
  .get({
63
- path: `${input.business_id}/owned_ad_accounts`,
100
+ path: `${businessId}/owned_ad_accounts`,
64
101
  params: { fields: "id,account_id,name,currency,account_status,amount_spent,balance,spend_cap", limit: input.max_ad_accounts },
65
102
  })
66
103
  .then(ok)
67
104
  .catch(fail);
68
105
  const clientAds = ctx.graph
69
106
  .get({
70
- path: `${input.business_id}/client_ad_accounts`,
107
+ path: `${businessId}/client_ad_accounts`,
71
108
  params: { fields: "id,account_id,name,currency,account_status", limit: input.max_ad_accounts },
72
109
  })
73
110
  .then(ok)
@@ -75,7 +112,7 @@ export async function handler(input, ctx) {
75
112
  const pixels = input.include_pixels
76
113
  ? ctx.graph
77
114
  .get({
78
- path: `${input.business_id}/owned_pixels`,
115
+ path: `${businessId}/owned_pixels`,
79
116
  params: { fields: "id,name,last_fired_time,is_unavailable", limit: 25 },
80
117
  })
81
118
  .then(ok)
@@ -84,7 +121,7 @@ export async function handler(input, ctx) {
84
121
  const catalogs = input.include_catalogs
85
122
  ? ctx.graph
86
123
  .get({
87
- path: `${input.business_id}/owned_product_catalogs`,
124
+ path: `${businessId}/owned_product_catalogs`,
88
125
  params: { fields: "id,name,vertical,product_count", limit: 25 },
89
126
  })
90
127
  .then(ok)
@@ -93,7 +130,7 @@ export async function handler(input, ctx) {
93
130
  const wabas = input.include_whatsapp
94
131
  ? ctx.graph
95
132
  .get({
96
- path: `${input.business_id}/owned_whatsapp_business_accounts`,
133
+ path: `${businessId}/owned_whatsapp_business_accounts`,
97
134
  params: { fields: "id,name,currency,status,business_verification_status", limit: 25 },
98
135
  })
99
136
  .then(ok)
@@ -180,7 +217,8 @@ export async function handler(input, ctx) {
180
217
  };
181
218
  }));
182
219
  const structured = {
183
- business_id: input.business_id,
220
+ business_id: businessId,
221
+ business_id_source: businessIdSource,
184
222
  date_preset: input.date_preset,
185
223
  generated_at: new Date().toISOString(),
186
224
  latency_ms: Date.now() - started,
@@ -0,0 +1,41 @@
1
+ import { z } from "zod";
2
+ import type { ToolContext } from "../../context.js";
3
+ export declare const inputSchema: z.ZodObject<{
4
+ include_instagram: z.ZodDefault<z.ZodBoolean>;
5
+ include_facebook: z.ZodDefault<z.ZodBoolean>;
6
+ max_pages: z.ZodDefault<z.ZodNumber>;
7
+ include_post_insights: z.ZodDefault<z.ZodBoolean>;
8
+ include_media_insights: z.ZodDefault<z.ZodBoolean>;
9
+ }, "strict", z.ZodTypeAny, {
10
+ max_pages: number;
11
+ include_instagram: boolean;
12
+ include_facebook: boolean;
13
+ include_post_insights: boolean;
14
+ include_media_insights: boolean;
15
+ }, {
16
+ max_pages?: number | undefined;
17
+ include_instagram?: boolean | undefined;
18
+ include_facebook?: boolean | undefined;
19
+ include_post_insights?: boolean | undefined;
20
+ include_media_insights?: boolean | undefined;
21
+ }>;
22
+ export type Input = z.infer<typeof inputSchema>;
23
+ export declare const definition: {
24
+ readonly name: "meta_latest_posts_summary";
25
+ readonly title: "Latest post performance across every social account";
26
+ readonly description: "Workflow tool that answers \"how did my latest post on each social account perform?\" in a single call.\n\nInternally walks:\n1. /me/assigned_pages → every Facebook Page assigned to this token\n2. For each Page: latest published post + (optionally) its insights (impressions, reach, engaged_users, clicks)\n3. For each Page: linked instagram_business_account\n4. For each IG: latest media + (optionally) its insights (reach, views, likes, comments, shares, saved, total_interactions)\n\nReturns a unified array of '{platform, owner, latest_post, insights}' entries, plus a per-section error map so a single failure doesn't blank the report.\n\n**Use this as the FIRST tool call** when the user asks anything like:\n- \"How are my social media accounts performing?\"\n- \"What's the engagement on my latest posts?\"\n- \"How did my most recent content do?\"\n\nIt removes the need to discover Page / IG IDs separately, and avoids the N+1 sequence of meta_page_list → meta_page_list_posts → meta_page_get_post_insights × pages × posts.";
27
+ readonly inputSchema: {
28
+ include_instagram: z.ZodDefault<z.ZodBoolean>;
29
+ include_facebook: z.ZodDefault<z.ZodBoolean>;
30
+ max_pages: z.ZodDefault<z.ZodNumber>;
31
+ include_post_insights: z.ZodDefault<z.ZodBoolean>;
32
+ include_media_insights: z.ZodDefault<z.ZodBoolean>;
33
+ };
34
+ readonly annotations: {
35
+ readonly readOnlyHint: true;
36
+ readonly destructiveHint: false;
37
+ readonly idempotentHint: true;
38
+ readonly openWorldHint: true;
39
+ };
40
+ };
41
+ export declare function handler(input: Input, ctx: ToolContext): Promise<import("../../helpers/format.js").ToolTextResult>;
@@ -0,0 +1,195 @@
1
+ import { z } from "zod";
2
+ import { MetaError } from "../../errors.js";
3
+ import { jsonBlock, toolResult } from "../../helpers/format.js";
4
+ export const inputSchema = z
5
+ .object({
6
+ include_instagram: z
7
+ .boolean()
8
+ .default(true)
9
+ .describe("Walk linked Instagram Business accounts via assigned Pages and include their latest media."),
10
+ include_facebook: z
11
+ .boolean()
12
+ .default(true)
13
+ .describe("Walk assigned Facebook Pages and include their latest post."),
14
+ max_pages: z
15
+ .number()
16
+ .int()
17
+ .min(1)
18
+ .max(20)
19
+ .default(10)
20
+ .describe("Cap on Pages walked. Each Page is one extra Graph call to fetch latest post + insights."),
21
+ include_post_insights: z
22
+ .boolean()
23
+ .default(true)
24
+ .describe("Include per-post insights (impressions, reach, engaged users). Disable to halve the Graph calls."),
25
+ include_media_insights: z
26
+ .boolean()
27
+ .default(true)
28
+ .describe("Include per-IG-media insights (reach, views, likes). Disable if your app lacks instagram_manage_insights Standard Access."),
29
+ })
30
+ .strict();
31
+ export const definition = {
32
+ name: "meta_latest_posts_summary",
33
+ title: "Latest post performance across every social account",
34
+ description: `Workflow tool that answers "how did my latest post on each social account perform?" in a single call.
35
+
36
+ Internally walks:
37
+ 1. /me/assigned_pages → every Facebook Page assigned to this token
38
+ 2. For each Page: latest published post + (optionally) its insights (impressions, reach, engaged_users, clicks)
39
+ 3. For each Page: linked instagram_business_account
40
+ 4. For each IG: latest media + (optionally) its insights (reach, views, likes, comments, shares, saved, total_interactions)
41
+
42
+ Returns a unified array of '{platform, owner, latest_post, insights}' entries, plus a per-section error map so a single failure doesn't blank the report.
43
+
44
+ **Use this as the FIRST tool call** when the user asks anything like:
45
+ - "How are my social media accounts performing?"
46
+ - "What's the engagement on my latest posts?"
47
+ - "How did my most recent content do?"
48
+
49
+ It removes the need to discover Page / IG IDs separately, and avoids the N+1 sequence of meta_page_list → meta_page_list_posts → meta_page_get_post_insights × pages × posts.`,
50
+ inputSchema: inputSchema.shape,
51
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
52
+ };
53
+ export async function handler(input, ctx) {
54
+ const started = Date.now();
55
+ const entries = [];
56
+ const errors = {};
57
+ // Step 1: list assigned Pages (used by both Facebook and Instagram paths).
58
+ let pages = [];
59
+ try {
60
+ const pagesResp = await ctx.graph.get({
61
+ path: "me/assigned_pages",
62
+ params: { fields: "id,name", limit: input.max_pages },
63
+ });
64
+ pages = Array.isArray(pagesResp.data) ? pagesResp.data : [];
65
+ }
66
+ catch (err) {
67
+ const e = err instanceof MetaError ? err : new MetaError(err.message);
68
+ errors["assigned_pages"] = { error: e.message, hint: e.hint };
69
+ }
70
+ // Step 2: for each Page, fetch latest published post + insights (in parallel).
71
+ if (input.include_facebook && pages.length > 0) {
72
+ const fbResults = await Promise.all(pages.map(async (p) => {
73
+ try {
74
+ const pageToken = await ctx.graph.getPageAccessToken(p.id);
75
+ const postsPage = await ctx.graph.get({
76
+ path: `${p.id}/published_posts`,
77
+ params: {
78
+ fields: "id,message,created_time,permalink_url,status_type,attachments{media_type,title,url},reactions.summary(true).limit(0),shares,comments.summary(true).limit(0)",
79
+ limit: 1,
80
+ },
81
+ accessTokenOverride: pageToken,
82
+ });
83
+ const latest = postsPage.data?.[0] ?? null;
84
+ let insights = null;
85
+ if (latest && input.include_post_insights) {
86
+ try {
87
+ const insResp = await ctx.graph.get({
88
+ path: `${latest.id}/insights`,
89
+ params: {
90
+ metric: "post_impressions,post_impressions_unique,post_engaged_users,post_clicks,post_reactions_by_type_total",
91
+ },
92
+ accessTokenOverride: pageToken,
93
+ });
94
+ insights = { data: insResp.data ?? [] };
95
+ }
96
+ catch (err) {
97
+ const e = err instanceof MetaError ? err : new MetaError(err.message);
98
+ insights = { error: e.message, hint: e.hint };
99
+ }
100
+ }
101
+ return {
102
+ platform: "facebook",
103
+ page_id: p.id,
104
+ page_name: p.name,
105
+ latest_post: latest,
106
+ insights,
107
+ };
108
+ }
109
+ catch (err) {
110
+ const e = err instanceof MetaError ? err : new MetaError(err.message);
111
+ return {
112
+ platform: "facebook",
113
+ page_id: p.id,
114
+ page_name: p.name,
115
+ latest_post: null,
116
+ insights: { error: e.message, hint: e.hint },
117
+ };
118
+ }
119
+ }));
120
+ entries.push(...fbResults);
121
+ }
122
+ // Step 3: resolve IG via each Page, then fetch latest media + insights.
123
+ if (input.include_instagram && pages.length > 0) {
124
+ const igLinks = await Promise.all(pages.map(async (p) => {
125
+ try {
126
+ const d = await ctx.graph.get({
127
+ path: p.id,
128
+ params: { fields: "id,name,instagram_business_account{id,username,name}" },
129
+ });
130
+ return d.instagram_business_account
131
+ ? { ig: d.instagram_business_account, sourcePage: p }
132
+ : null;
133
+ }
134
+ catch {
135
+ return null;
136
+ }
137
+ }));
138
+ const igAccounts = igLinks.filter((x) => x !== null);
139
+ const igResults = await Promise.all(igAccounts.map(async (link) => {
140
+ try {
141
+ const mediaPage = await ctx.graph.get({
142
+ path: `${link.ig.id}/media`,
143
+ params: {
144
+ fields: "id,media_type,media_product_type,caption,media_url,thumbnail_url,permalink,timestamp,like_count,comments_count",
145
+ limit: 1,
146
+ },
147
+ });
148
+ const latest = mediaPage.data?.[0] ?? null;
149
+ let insights = null;
150
+ if (latest && input.include_media_insights) {
151
+ try {
152
+ const insResp = await ctx.graph.get({
153
+ path: `${latest.id}/insights`,
154
+ params: { metric: "reach,views,likes,comments,shares,saved,total_interactions" },
155
+ });
156
+ insights = { data: insResp.data ?? [] };
157
+ }
158
+ catch (err) {
159
+ const e = err instanceof MetaError ? err : new MetaError(err.message);
160
+ insights = { error: e.message, hint: e.hint };
161
+ }
162
+ }
163
+ return {
164
+ platform: "instagram",
165
+ ig_user_id: link.ig.id,
166
+ ig_username: link.ig.username,
167
+ page_id: link.sourcePage.id,
168
+ page_name: link.sourcePage.name,
169
+ latest_post: latest,
170
+ insights,
171
+ };
172
+ }
173
+ catch (err) {
174
+ const e = err instanceof MetaError ? err : new MetaError(err.message);
175
+ return {
176
+ platform: "instagram",
177
+ ig_user_id: link.ig.id,
178
+ ig_username: link.ig.username,
179
+ latest_post: null,
180
+ insights: { error: e.message, hint: e.hint },
181
+ };
182
+ }
183
+ }));
184
+ entries.push(...igResults);
185
+ }
186
+ const structured = {
187
+ generated_at: new Date().toISOString(),
188
+ latency_ms: Date.now() - started,
189
+ facebook_count: entries.filter((e) => e.platform === "facebook").length,
190
+ instagram_count: entries.filter((e) => e.platform === "instagram").length,
191
+ entries,
192
+ errors: Object.keys(errors).length > 0 ? errors : null,
193
+ };
194
+ return toolResult(structured, jsonBlock(structured));
195
+ }
@@ -11,10 +11,10 @@ export declare const inputSchema: z.ZodObject<{
11
11
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
12
12
  }, "strict", z.ZodTypeAny, {
13
13
  fields: string[];
14
+ page_id: string;
14
15
  limit: number;
15
16
  auto_paginate: boolean;
16
17
  edge: "posts" | "published_posts" | "feed";
17
- page_id: string;
18
18
  since?: string | undefined;
19
19
  until?: string | undefined;
20
20
  after?: string | undefined;
@@ -8,9 +8,9 @@ export declare const inputSchema: z.ZodObject<{
8
8
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
9
9
  }, "strict", z.ZodTypeAny, {
10
10
  fields: string[];
11
+ page_id: string;
11
12
  limit: number;
12
13
  auto_paginate: boolean;
13
- page_id: string;
14
14
  after?: string | undefined;
15
15
  }, {
16
16
  page_id: string;
@@ -8,9 +8,9 @@ export declare const inputSchema: z.ZodObject<{
8
8
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
9
9
  }, "strict", z.ZodTypeAny, {
10
10
  fields: string[];
11
+ page_id: string;
11
12
  limit: number;
12
13
  auto_paginate: boolean;
13
- page_id: string;
14
14
  after?: string | undefined;
15
15
  }, {
16
16
  page_id: string;
@@ -8,9 +8,9 @@ export declare const inputSchema: z.ZodObject<{
8
8
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
9
9
  }, "strict", z.ZodTypeAny, {
10
10
  fields: string[];
11
+ business_id: string;
11
12
  limit: number;
12
13
  auto_paginate: boolean;
13
- business_id: string;
14
14
  after?: string | undefined;
15
15
  }, {
16
16
  business_id: string;
@@ -45,6 +45,7 @@ import * as whatsappListTemplates from "./whatsapp/list-templates.js";
45
45
  import * as whatsappGetAnalytics from "./whatsapp/get-analytics.js";
46
46
  // Overview
47
47
  import * as businessOverview from "./overview/business-overview.js";
48
+ import * as latestPostsSummary from "./overview/latest-posts-summary.js";
48
49
  const TOOLS = [
49
50
  // Token + meta (3)
50
51
  tokenInspect, tokenHealth, graphRead,
@@ -64,8 +65,8 @@ const TOOLS = [
64
65
  catalogList, catalogListProducts, catalogGetDiagnostics,
65
66
  // WhatsApp (4)
66
67
  whatsappListWabas, whatsappListPhoneNumbers, whatsappListTemplates, whatsappGetAnalytics,
67
- // Overview (1)
68
- businessOverview,
68
+ // Overview (2)
69
+ businessOverview, latestPostsSummary,
69
70
  ].map((m) => m);
70
71
  export function registerTools(server, ctx) {
71
72
  const names = [];
@@ -8,9 +8,9 @@ export declare const inputSchema: z.ZodObject<{
8
8
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
9
9
  }, "strict", z.ZodTypeAny, {
10
10
  fields: string[];
11
+ waba_id: string;
11
12
  limit: number;
12
13
  auto_paginate: boolean;
13
- waba_id: string;
14
14
  after?: string | undefined;
15
15
  }, {
16
16
  waba_id: string;
@@ -9,9 +9,9 @@ export declare const inputSchema: z.ZodObject<{
9
9
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
10
10
  }, "strict", z.ZodTypeAny, {
11
11
  fields: string[];
12
+ waba_id: string;
12
13
  limit: number;
13
14
  auto_paginate: boolean;
14
- waba_id: string;
15
15
  status?: "PAUSED" | "DELETED" | "APPROVED" | "PENDING" | "REJECTED" | "DISABLED" | "IN_APPEAL" | undefined;
16
16
  after?: string | undefined;
17
17
  }, {
@@ -9,9 +9,9 @@ export declare const inputSchema: z.ZodObject<{
9
9
  fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
10
10
  }, "strict", z.ZodTypeAny, {
11
11
  fields: string[];
12
+ business_id: string;
12
13
  limit: number;
13
14
  auto_paginate: boolean;
14
- business_id: string;
15
15
  scope: "owned" | "client" | "both";
16
16
  after?: string | undefined;
17
17
  }, {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@praise25/meta-mcp-server",
3
3
  "description": "Read-only Model Context Protocol server for Meta Business Manager — Pages, Instagram, Ads insights, Pixels, Catalog, WhatsApp.",
4
- "version": "0.1.6",
4
+ "version": "0.1.8",
5
5
  "author": "Stephen A.",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/feladeveloper/meta-mcp-server#readme",