@cashtokenai/meta-mcp-server 1.0.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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +294 -0
  3. package/dist/config.d.ts +39 -0
  4. package/dist/config.js +60 -0
  5. package/dist/constants.d.ts +21 -0
  6. package/dist/constants.js +28 -0
  7. package/dist/context.d.ts +9 -0
  8. package/dist/context.js +8 -0
  9. package/dist/errors.d.ts +41 -0
  10. package/dist/errors.js +120 -0
  11. package/dist/helpers/cache.d.ts +6 -0
  12. package/dist/helpers/cache.js +28 -0
  13. package/dist/helpers/format.d.ts +17 -0
  14. package/dist/helpers/format.js +28 -0
  15. package/dist/helpers/graph-client.d.ts +90 -0
  16. package/dist/helpers/graph-client.js +219 -0
  17. package/dist/helpers/schema.d.ts +30 -0
  18. package/dist/helpers/schema.js +69 -0
  19. package/dist/helpers/validate.d.ts +26 -0
  20. package/dist/helpers/validate.js +125 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +36 -0
  23. package/dist/logger.d.ts +3 -0
  24. package/dist/logger.js +18 -0
  25. package/dist/server.d.ts +7 -0
  26. package/dist/server.js +34 -0
  27. package/dist/tools/ads/get-account.d.ts +29 -0
  28. package/dist/tools/ads/get-account.js +45 -0
  29. package/dist/tools/ads/get-creative.d.ts +29 -0
  30. package/dist/tools/ads/get-creative.js +37 -0
  31. package/dist/tools/ads/get-insights.d.ts +129 -0
  32. package/dist/tools/ads/get-insights.js +153 -0
  33. package/dist/tools/ads/list-accounts.d.ts +54 -0
  34. package/dist/tools/ads/list-accounts.js +59 -0
  35. package/dist/tools/ads/list-ads.d.ts +53 -0
  36. package/dist/tools/ads/list-ads.js +59 -0
  37. package/dist/tools/ads/list-adsets.d.ts +49 -0
  38. package/dist/tools/ads/list-adsets.js +54 -0
  39. package/dist/tools/ads/list-campaigns.d.ts +45 -0
  40. package/dist/tools/ads/list-campaigns.js +64 -0
  41. package/dist/tools/ads/list-custom-audiences.d.ts +41 -0
  42. package/dist/tools/ads/list-custom-audiences.js +41 -0
  43. package/dist/tools/business/list-assets.d.ts +37 -0
  44. package/dist/tools/business/list-assets.js +136 -0
  45. package/dist/tools/business/list-businesses.d.ts +37 -0
  46. package/dist/tools/business/list-businesses.js +81 -0
  47. package/dist/tools/business/list-system-users.d.ts +41 -0
  48. package/dist/tools/business/list-system-users.js +73 -0
  49. package/dist/tools/catalog/get-diagnostics.d.ts +29 -0
  50. package/dist/tools/catalog/get-diagnostics.js +26 -0
  51. package/dist/tools/catalog/list-products.d.ts +45 -0
  52. package/dist/tools/catalog/list-products.js +49 -0
  53. package/dist/tools/catalog/list.d.ts +54 -0
  54. package/dist/tools/catalog/list.js +48 -0
  55. package/dist/tools/instagram/get-account.d.ts +29 -0
  56. package/dist/tools/instagram/get-account.js +34 -0
  57. package/dist/tools/instagram/get-audience-demographics.d.ts +45 -0
  58. package/dist/tools/instagram/get-audience-demographics.js +58 -0
  59. package/dist/tools/instagram/get-media-insights.d.ts +29 -0
  60. package/dist/tools/instagram/get-media-insights.js +49 -0
  61. package/dist/tools/instagram/list-accounts.d.ts +33 -0
  62. package/dist/tools/instagram/list-accounts.js +63 -0
  63. package/dist/tools/instagram/list-media.d.ts +41 -0
  64. package/dist/tools/instagram/list-media.js +42 -0
  65. package/dist/tools/meta/graph-read.d.ts +33 -0
  66. package/dist/tools/meta/graph-read.js +71 -0
  67. package/dist/tools/overview/business-overview.d.ts +49 -0
  68. package/dist/tools/overview/business-overview.js +235 -0
  69. package/dist/tools/overview/content-report.d.ts +57 -0
  70. package/dist/tools/overview/content-report.js +344 -0
  71. package/dist/tools/overview/latest-posts-summary.d.ts +41 -0
  72. package/dist/tools/overview/latest-posts-summary.js +189 -0
  73. package/dist/tools/pages/get-insights.d.ts +41 -0
  74. package/dist/tools/pages/get-insights.js +49 -0
  75. package/dist/tools/pages/get-post-insights.d.ts +33 -0
  76. package/dist/tools/pages/get-post-insights.js +47 -0
  77. package/dist/tools/pages/get.d.ts +29 -0
  78. package/dist/tools/pages/get.js +50 -0
  79. package/dist/tools/pages/list-posts.d.ts +53 -0
  80. package/dist/tools/pages/list-posts.js +62 -0
  81. package/dist/tools/pages/list-reviews.d.ts +41 -0
  82. package/dist/tools/pages/list-reviews.js +37 -0
  83. package/dist/tools/pages/list-videos.d.ts +41 -0
  84. package/dist/tools/pages/list-videos.js +40 -0
  85. package/dist/tools/pages/list.d.ts +41 -0
  86. package/dist/tools/pages/list.js +39 -0
  87. package/dist/tools/pixels/get-stats.d.ts +41 -0
  88. package/dist/tools/pixels/get-stats.js +34 -0
  89. package/dist/tools/pixels/list.d.ts +41 -0
  90. package/dist/tools/pixels/list.js +41 -0
  91. package/dist/tools/register.d.ts +3 -0
  92. package/dist/tools/register.js +95 -0
  93. package/dist/tools/shared.d.ts +45 -0
  94. package/dist/tools/shared.js +137 -0
  95. package/dist/tools/token/health.d.ts +17 -0
  96. package/dist/tools/token/health.js +65 -0
  97. package/dist/tools/token/inspect.d.ts +26 -0
  98. package/dist/tools/token/inspect.js +88 -0
  99. package/dist/tools/whatsapp/get-analytics.d.ts +57 -0
  100. package/dist/tools/whatsapp/get-analytics.js +66 -0
  101. package/dist/tools/whatsapp/list-phone-numbers.d.ts +41 -0
  102. package/dist/tools/whatsapp/list-phone-numbers.js +35 -0
  103. package/dist/tools/whatsapp/list-templates.d.ts +45 -0
  104. package/dist/tools/whatsapp/list-templates.js +44 -0
  105. package/dist/tools/whatsapp/list-wabas.d.ts +54 -0
  106. package/dist/tools/whatsapp/list-wabas.js +48 -0
  107. package/dist/types/meta.d.ts +46 -0
  108. package/dist/types/meta.js +1 -0
  109. package/package.json +77 -0
@@ -0,0 +1,41 @@
1
+ import { z } from "zod";
2
+ import type { ToolContext } from "../../context.js";
3
+ export declare const inputSchema: z.ZodObject<{
4
+ limit: z.ZodDefault<z.ZodNumber>;
5
+ after: z.ZodOptional<z.ZodString>;
6
+ auto_paginate: z.ZodDefault<z.ZodBoolean>;
7
+ business_id: z.ZodString;
8
+ fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
9
+ }, "strict", z.ZodTypeAny, {
10
+ fields: string[];
11
+ business_id: string;
12
+ limit: number;
13
+ auto_paginate: boolean;
14
+ after?: string | undefined;
15
+ }, {
16
+ business_id: string;
17
+ fields?: string[] | undefined;
18
+ limit?: number | undefined;
19
+ after?: string | undefined;
20
+ auto_paginate?: boolean | undefined;
21
+ }>;
22
+ export type Input = z.infer<typeof inputSchema>;
23
+ export declare const definition: {
24
+ readonly name: "meta_pixel_list";
25
+ readonly title: "List Meta Pixels under a business";
26
+ readonly description: "Lists Meta (Facebook) Pixels owned by a business via /{business_id}/owned_pixels. Includes last_fired_time so you can spot stale pixels. Requires 'business_management' or Marketing API access.";
27
+ readonly inputSchema: {
28
+ limit: z.ZodDefault<z.ZodNumber>;
29
+ after: z.ZodOptional<z.ZodString>;
30
+ auto_paginate: z.ZodDefault<z.ZodBoolean>;
31
+ business_id: z.ZodString;
32
+ fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
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,41 @@
1
+ import { z } from "zod";
2
+ import { assertAllowed } from "../../config.js";
3
+ import { metaIdSchema, paginationShape } from "../../helpers/schema.js";
4
+ import { runList } from "../shared.js";
5
+ export const inputSchema = z
6
+ .object({
7
+ business_id: metaIdSchema.describe("Business Manager ID."),
8
+ fields: z
9
+ .array(z.string())
10
+ .default([
11
+ // 'code' (the pixel's JS snippet) and 'owner_ad_account' both require
12
+ // ads_management on the *owning* ad account, not just the business.
13
+ // Reading them with a system-user token that wasn't granted that
14
+ // task fails the whole list with (#200). We omit them from the
15
+ // defaults so the list stays useful; callers can re-add explicitly.
16
+ "id",
17
+ "name",
18
+ "is_created_by_business",
19
+ "is_unavailable",
20
+ "last_fired_time",
21
+ "creation_time",
22
+ "owner_business",
23
+ ])
24
+ .describe("Pixel fields. 'code' and 'owner_ad_account' are NOT in the default — they require ads_management on the owning ad account. Re-add them only when the system user has that task on every pixel's owner account."),
25
+ ...paginationShape,
26
+ })
27
+ .strict();
28
+ export const definition = {
29
+ name: "meta_pixel_list",
30
+ title: "List Meta Pixels under a business",
31
+ description: `Lists Meta (Facebook) Pixels owned by a business via /{business_id}/owned_pixels. Includes last_fired_time so you can spot stale pixels. Requires 'business_management' or Marketing API access.`,
32
+ inputSchema: inputSchema.shape,
33
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
34
+ };
35
+ export async function handler(input, ctx) {
36
+ assertAllowed("business", input.business_id, ctx.config);
37
+ return runList(ctx, {
38
+ path: `${input.business_id}/owned_pixels`,
39
+ params: { fields: input.fields.join(","), limit: input.limit, after: input.after },
40
+ }, { auto_paginate: input.auto_paginate, after: input.after, limit: input.limit }, { business_id: input.business_id });
41
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ToolContext } from "../context.js";
3
+ export declare function registerTools(server: McpServer, ctx: ToolContext): string[];
@@ -0,0 +1,95 @@
1
+ import { z } from "zod";
2
+ import { validateInput } from "../helpers/validate.js";
3
+ // Token + meta
4
+ import * as tokenInspect from "./token/inspect.js";
5
+ import * as tokenHealth from "./token/health.js";
6
+ import * as graphRead from "./meta/graph-read.js";
7
+ // Business discovery
8
+ import * as listBusinesses from "./business/list-businesses.js";
9
+ import * as listAssets from "./business/list-assets.js";
10
+ import * as listSystemUsers from "./business/list-system-users.js";
11
+ // Ads (Marketing API)
12
+ import * as adsListAccounts from "./ads/list-accounts.js";
13
+ import * as adsGetAccount from "./ads/get-account.js";
14
+ import * as adsListCampaigns from "./ads/list-campaigns.js";
15
+ import * as adsListAdsets from "./ads/list-adsets.js";
16
+ import * as adsListAds from "./ads/list-ads.js";
17
+ import * as adsGetInsights from "./ads/get-insights.js";
18
+ import * as adsGetCreative from "./ads/get-creative.js";
19
+ import * as adsListCustomAudiences from "./ads/list-custom-audiences.js";
20
+ // Pages
21
+ import * as pageList from "./pages/list.js";
22
+ import * as pageGet from "./pages/get.js";
23
+ import * as pageListPosts from "./pages/list-posts.js";
24
+ import * as pageGetPostInsights from "./pages/get-post-insights.js";
25
+ import * as pageGetInsights from "./pages/get-insights.js";
26
+ import * as pageListReviews from "./pages/list-reviews.js";
27
+ import * as pageListVideos from "./pages/list-videos.js";
28
+ // Instagram
29
+ import * as igListAccounts from "./instagram/list-accounts.js";
30
+ import * as igGetAccount from "./instagram/get-account.js";
31
+ import * as igListMedia from "./instagram/list-media.js";
32
+ import * as igGetMediaInsights from "./instagram/get-media-insights.js";
33
+ import * as igGetAudienceDemographics from "./instagram/get-audience-demographics.js";
34
+ // Pixels
35
+ import * as pixelList from "./pixels/list.js";
36
+ import * as pixelGetStats from "./pixels/get-stats.js";
37
+ // Catalog
38
+ import * as catalogList from "./catalog/list.js";
39
+ import * as catalogListProducts from "./catalog/list-products.js";
40
+ import * as catalogGetDiagnostics from "./catalog/get-diagnostics.js";
41
+ // WhatsApp
42
+ import * as whatsappListWabas from "./whatsapp/list-wabas.js";
43
+ import * as whatsappListPhoneNumbers from "./whatsapp/list-phone-numbers.js";
44
+ import * as whatsappListTemplates from "./whatsapp/list-templates.js";
45
+ import * as whatsappGetAnalytics from "./whatsapp/get-analytics.js";
46
+ // Overview
47
+ import * as businessOverview from "./overview/business-overview.js";
48
+ import * as latestPostsSummary from "./overview/latest-posts-summary.js";
49
+ import * as contentReport from "./overview/content-report.js";
50
+ const TOOLS = [
51
+ // Token + meta (3)
52
+ tokenInspect, tokenHealth, graphRead,
53
+ // Business discovery (3)
54
+ listBusinesses, listAssets, listSystemUsers,
55
+ // Ads (8)
56
+ adsListAccounts, adsGetAccount, adsListCampaigns, adsListAdsets, adsListAds,
57
+ adsGetInsights, adsGetCreative, adsListCustomAudiences,
58
+ // Pages (7)
59
+ pageList, pageGet, pageListPosts, pageGetPostInsights, pageGetInsights,
60
+ pageListReviews, pageListVideos,
61
+ // Instagram (5)
62
+ igListAccounts, igGetAccount, igListMedia, igGetMediaInsights, igGetAudienceDemographics,
63
+ // Pixels (2)
64
+ pixelList, pixelGetStats,
65
+ // Catalog (3)
66
+ catalogList, catalogListProducts, catalogGetDiagnostics,
67
+ // WhatsApp (4)
68
+ whatsappListWabas, whatsappListPhoneNumbers, whatsappListTemplates, whatsappGetAnalytics,
69
+ // Overview (3)
70
+ businessOverview, latestPostsSummary, contentReport,
71
+ ].map((m) => m);
72
+ export function registerTools(server, ctx) {
73
+ const names = [];
74
+ for (const mod of TOOLS) {
75
+ server.registerTool(mod.definition.name, {
76
+ title: mod.definition.title,
77
+ description: mod.definition.description,
78
+ inputSchema: mod.definition.inputSchema,
79
+ annotations: mod.definition.annotations,
80
+ }, (async (args) => {
81
+ // Defense in depth: re-validate at the handler boundary even if the
82
+ // SDK / MCP gateway already did. Some gateways forward inputs without
83
+ // enforcing per-property Zod refinements (e.g. regex on ad_account_id),
84
+ // which would otherwise let placeholder strings like "act_<YOUR_PAGE_ID>"
85
+ // reach Meta. See ADR-20260429-Handler-Input-Validation.md.
86
+ const validated = validateInput(mod.inputSchema ?? z.object(mod.definition.inputSchema), args);
87
+ if (!validated.ok) {
88
+ return validated.error;
89
+ }
90
+ return mod.handler(validated.data, ctx);
91
+ }));
92
+ names.push(mod.definition.name);
93
+ }
94
+ return names;
95
+ }
@@ -0,0 +1,45 @@
1
+ import type { ToolContext } from "../context.js";
2
+ import { type ToolTextResult } from "../helpers/format.js";
3
+ import type { GraphGetOptions } from "../helpers/graph-client.js";
4
+ export interface ListRunOpts {
5
+ auto_paginate?: boolean;
6
+ after?: string;
7
+ limit?: number;
8
+ }
9
+ /**
10
+ * Fetch one page or auto-paginate, normalize into a structured list payload,
11
+ * and translate errors. Use for simple `GET /{id}/{edge}` style tools.
12
+ */
13
+ export declare function runList<T>(ctx: ToolContext, opts: GraphGetOptions, pag: ListRunOpts, extra?: Record<string, unknown>): Promise<ToolTextResult>;
14
+ /** Run a single read and return a tool result. */
15
+ export declare function runGet<T>(ctx: ToolContext, opts: GraphGetOptions, extra?: Record<string, unknown>): Promise<ToolTextResult>;
16
+ /**
17
+ * Like runGet, but routes through the secondary insights-app token + secret
18
+ * when one is configured (META_INSIGHTS_ACCESS_TOKEN). Use for direct
19
+ * `/{id}/insights` reads that require a permission (e.g.
20
+ * instagram_manage_insights) the primary ads app cannot host. Falls back to
21
+ * the primary token transparently when no insights app is configured.
22
+ */
23
+ export declare function runGetViaInsightsApp<T>(ctx: ToolContext, opts: GraphGetOptions, extra?: Record<string, unknown>): Promise<ToolTextResult>;
24
+ export declare function errorResult(err: unknown): ToolTextResult;
25
+ /**
26
+ * Resolves a Page access token for `pageId`, then runs `runList` with it as
27
+ * the access-token override. Page-level edges (`/{page_id}/posts`,
28
+ * `/{page_id}/insights`, `/{page_id}/ratings`, `/{page_id}/videos`,
29
+ * `/{post_id}/insights`) require a Page access token rather than the
30
+ * configured system-user token.
31
+ */
32
+ export declare function runListAsPage<T>(ctx: ToolContext, pageId: string, opts: GraphGetOptions, pag: ListRunOpts, extra?: Record<string, unknown>): Promise<ToolTextResult>;
33
+ /** Same as runGet but resolves and uses the Page access token first. */
34
+ export declare function runGetAsPage<T>(ctx: ToolContext, pageId: string, opts: GraphGetOptions, extra?: Record<string, unknown>): Promise<ToolTextResult>;
35
+ /**
36
+ * Page-insights variant of runGetAsPage. When a secondary insights app is
37
+ * configured (the app holding the Pages "read_insights" use case), the Page
38
+ * access token is derived from the *insights* app's token and the call's
39
+ * appsecret_proof uses the insights app secret. Falls back to the primary
40
+ * app's Page token when no insights app is configured.
41
+ *
42
+ * Use for /{page_id}/insights and /{post_id}/insights, which need
43
+ * `read_insights` — a permission the Marketing-API primary app cannot host.
44
+ */
45
+ export declare function runGetAsPageViaInsightsApp<T>(ctx: ToolContext, pageId: string, opts: GraphGetOptions, extra?: Record<string, unknown>): Promise<ToolTextResult>;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Shared helpers used across domain tools — keeps each tool file focused on
3
+ * its endpoint instead of re-implementing boilerplate.
4
+ */
5
+ import { insightsCreds } from "../config.js";
6
+ import { MetaError } from "../errors.js";
7
+ import { jsonBlock, toolError, toolResult } from "../helpers/format.js";
8
+ /**
9
+ * If a secondary "insights app" is configured, returns the token + app-secret
10
+ * overrides to route this call through it; otherwise returns an empty object
11
+ * (call uses the primary app). Insight endpoints (IG media/audience, Page/post
12
+ * insights) need this because Meta forbids combining the Marketing-API use
13
+ * case with Instagram-content / Pages-everything on a single app — so insights
14
+ * frequently live on a second app. See config.ts `insightsCreds`.
15
+ */
16
+ function insightsOverrides(ctx) {
17
+ const creds = insightsCreds(ctx.config);
18
+ if (!creds)
19
+ return {};
20
+ return { accessTokenOverride: creds.token, appSecretOverride: creds.appSecret };
21
+ }
22
+ /**
23
+ * Fetch one page or auto-paginate, normalize into a structured list payload,
24
+ * and translate errors. Use for simple `GET /{id}/{edge}` style tools.
25
+ */
26
+ export async function runList(ctx, opts, pag, extra = {}) {
27
+ try {
28
+ if (pag.auto_paginate) {
29
+ const { data, pages, nextAfter } = await ctx.graph.getAllPages(opts);
30
+ const structured = {
31
+ ...extra,
32
+ count: data.length,
33
+ pages_fetched: pages,
34
+ next_after: nextAfter,
35
+ items: data,
36
+ };
37
+ return toolResult(structured, jsonBlock(structured));
38
+ }
39
+ const page = await ctx.graph.get(opts);
40
+ const items = Array.isArray(page.data) ? page.data : [];
41
+ const structured = {
42
+ ...extra,
43
+ count: items.length,
44
+ pages_fetched: 1,
45
+ next_after: page.paging?.cursors?.after,
46
+ items,
47
+ };
48
+ return toolResult(structured, jsonBlock(structured));
49
+ }
50
+ catch (err) {
51
+ return errorResult(err);
52
+ }
53
+ }
54
+ /** Run a single read and return a tool result. */
55
+ export async function runGet(ctx, opts, extra = {}) {
56
+ try {
57
+ const data = await ctx.graph.get(opts);
58
+ const structured = { ...extra, data };
59
+ return toolResult(structured, jsonBlock(data));
60
+ }
61
+ catch (err) {
62
+ return errorResult(err);
63
+ }
64
+ }
65
+ /**
66
+ * Like runGet, but routes through the secondary insights-app token + secret
67
+ * when one is configured (META_INSIGHTS_ACCESS_TOKEN). Use for direct
68
+ * `/{id}/insights` reads that require a permission (e.g.
69
+ * instagram_manage_insights) the primary ads app cannot host. Falls back to
70
+ * the primary token transparently when no insights app is configured.
71
+ */
72
+ export async function runGetViaInsightsApp(ctx, opts, extra = {}) {
73
+ return runGet(ctx, { ...opts, ...insightsOverrides(ctx) }, extra);
74
+ }
75
+ export function errorResult(err) {
76
+ const e = err instanceof MetaError ? err : new MetaError(err.message);
77
+ return toolError(e.message, e.hint, {
78
+ code: e.code,
79
+ subcode: e.subcode,
80
+ fbtrace_id: e.fbtraceId,
81
+ http_status: e.httpStatus,
82
+ retryable: e.retryable,
83
+ });
84
+ }
85
+ /**
86
+ * Resolves a Page access token for `pageId`, then runs `runList` with it as
87
+ * the access-token override. Page-level edges (`/{page_id}/posts`,
88
+ * `/{page_id}/insights`, `/{page_id}/ratings`, `/{page_id}/videos`,
89
+ * `/{post_id}/insights`) require a Page access token rather than the
90
+ * configured system-user token.
91
+ */
92
+ export async function runListAsPage(ctx, pageId, opts, pag, extra = {}) {
93
+ try {
94
+ const pageToken = await ctx.graph.getPageAccessToken(pageId);
95
+ return runList(ctx, { ...opts, accessTokenOverride: pageToken }, pag, extra);
96
+ }
97
+ catch (err) {
98
+ return errorResult(err);
99
+ }
100
+ }
101
+ /** Same as runGet but resolves and uses the Page access token first. */
102
+ export async function runGetAsPage(ctx, pageId, opts, extra = {}) {
103
+ try {
104
+ const pageToken = await ctx.graph.getPageAccessToken(pageId);
105
+ return runGet(ctx, { ...opts, accessTokenOverride: pageToken }, extra);
106
+ }
107
+ catch (err) {
108
+ return errorResult(err);
109
+ }
110
+ }
111
+ /**
112
+ * Page-insights variant of runGetAsPage. When a secondary insights app is
113
+ * configured (the app holding the Pages "read_insights" use case), the Page
114
+ * access token is derived from the *insights* app's token and the call's
115
+ * appsecret_proof uses the insights app secret. Falls back to the primary
116
+ * app's Page token when no insights app is configured.
117
+ *
118
+ * Use for /{page_id}/insights and /{post_id}/insights, which need
119
+ * `read_insights` — a permission the Marketing-API primary app cannot host.
120
+ */
121
+ export async function runGetAsPageViaInsightsApp(ctx, pageId, opts, extra = {}) {
122
+ try {
123
+ const creds = insightsCreds(ctx.config);
124
+ if (creds) {
125
+ const pageToken = await ctx.graph.getPageAccessToken(pageId, {
126
+ token: creds.token,
127
+ appSecret: creds.appSecret,
128
+ });
129
+ return runGet(ctx, { ...opts, accessTokenOverride: pageToken, appSecretOverride: creds.appSecret }, extra);
130
+ }
131
+ const pageToken = await ctx.graph.getPageAccessToken(pageId);
132
+ return runGet(ctx, { ...opts, accessTokenOverride: pageToken }, extra);
133
+ }
134
+ catch (err) {
135
+ return errorResult(err);
136
+ }
137
+ }
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ import type { ToolContext } from "../../context.js";
3
+ export declare const inputSchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
4
+ export type Input = z.infer<typeof inputSchema>;
5
+ export declare const definition: {
6
+ readonly name: "meta_health_check";
7
+ readonly title: "Meta MCP health check";
8
+ readonly description: "End-to-end reachability probe:\n- Reports the running server name + version (use this to confirm the gateway is on the latest published build — a stale npx cache is a common cause of \"missing\" tools).\n- Confirms the Graph API is reachable.\n- Confirms the configured token resolves to a valid identity (via /me).\n- Surfaces the latest rate-limit header snapshot from the Graph client.\n- Lists which allowlists are active (business / ad account / page / IG user).\n\nUse when a session starts, or when diagnosing why other tools are returning errors or appear to be missing.";
9
+ readonly inputSchema: {};
10
+ readonly annotations: {
11
+ readonly readOnlyHint: true;
12
+ readonly destructiveHint: false;
13
+ readonly idempotentHint: true;
14
+ readonly openWorldHint: true;
15
+ };
16
+ };
17
+ export declare function handler(_input: Input, ctx: ToolContext): Promise<import("../../helpers/format.js").ToolTextResult>;
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ import { SERVER_NAME, SERVER_VERSION } from "../../constants.js";
3
+ import { MetaError } from "../../errors.js";
4
+ import { jsonBlock, toolError, toolResult } from "../../helpers/format.js";
5
+ export const inputSchema = z.object({}).strict();
6
+ export const definition = {
7
+ name: "meta_health_check",
8
+ title: "Meta MCP health check",
9
+ description: `End-to-end reachability probe:
10
+ - Reports the running server name + version (use this to confirm the gateway is on the latest published build — a stale npx cache is a common cause of "missing" tools).
11
+ - Confirms the Graph API is reachable.
12
+ - Confirms the configured token resolves to a valid identity (via /me).
13
+ - Surfaces the latest rate-limit header snapshot from the Graph client.
14
+ - Lists which allowlists are active (business / ad account / page / IG user).
15
+
16
+ Use when a session starts, or when diagnosing why other tools are returning errors or appear to be missing.`,
17
+ inputSchema: inputSchema.shape,
18
+ annotations: {
19
+ readOnlyHint: true,
20
+ destructiveHint: false,
21
+ idempotentHint: true,
22
+ openWorldHint: true,
23
+ },
24
+ };
25
+ export async function handler(_input, ctx) {
26
+ const started = Date.now();
27
+ try {
28
+ const me = await ctx.graph.get({
29
+ path: "me",
30
+ params: { fields: "id,name" },
31
+ noCache: true,
32
+ });
33
+ const structured = {
34
+ ok: true,
35
+ server_name: SERVER_NAME,
36
+ server_version: SERVER_VERSION,
37
+ latency_ms: Date.now() - started,
38
+ identity: me,
39
+ api_version: ctx.config.apiVersion,
40
+ appsecret_proof_enabled: Boolean(ctx.config.appSecret),
41
+ insights_app_configured: Boolean(ctx.config.insightsAccessToken),
42
+ insights_app_appsecret_proof_enabled: Boolean(ctx.config.insightsAppSecret),
43
+ rate_limit: ctx.graph.rateLimit,
44
+ allowlists: {
45
+ businesses: ctx.config.allowedBusinessIds ? [...ctx.config.allowedBusinessIds] : null,
46
+ ad_accounts: ctx.config.allowedAdAccountIds
47
+ ? [...ctx.config.allowedAdAccountIds]
48
+ : null,
49
+ pages: ctx.config.allowedPageIds ? [...ctx.config.allowedPageIds] : null,
50
+ ig_users: ctx.config.allowedIgUserIds ? [...ctx.config.allowedIgUserIds] : null,
51
+ },
52
+ };
53
+ return toolResult(structured, jsonBlock(structured));
54
+ }
55
+ catch (err) {
56
+ const e = err instanceof MetaError ? err : new MetaError(err.message);
57
+ return toolError(`Health check failed: ${e.message}`, e.hint, {
58
+ code: e.code,
59
+ subcode: e.subcode,
60
+ fbtrace_id: e.fbtraceId,
61
+ http_status: e.httpStatus,
62
+ latency_ms: Date.now() - started,
63
+ });
64
+ }
65
+ }
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ import type { ToolContext } from "../../context.js";
3
+ import { ResponseFormat } from "../../helpers/format.js";
4
+ export declare const inputSchema: z.ZodObject<{
5
+ response_format: z.ZodDefault<z.ZodNativeEnum<typeof ResponseFormat>>;
6
+ }, "strict", z.ZodTypeAny, {
7
+ response_format: ResponseFormat;
8
+ }, {
9
+ response_format?: ResponseFormat | undefined;
10
+ }>;
11
+ export type Input = z.infer<typeof inputSchema>;
12
+ export declare const definition: {
13
+ readonly name: "meta_token_inspect";
14
+ readonly title: "Inspect Meta access token";
15
+ readonly description: "Decodes the configured META_ACCESS_TOKEN via Graph API /debug_token.\n\nReturns:\n- app_id + application name\n- token type (system-user / user / page)\n- is_valid\n- expires_at + data_access_expires_at (unix seconds; 0 = never expires)\n- scopes + granular_scopes (per-asset targeting)\n\nUse this first when troubleshooting — almost every other tool failure traces back to a missing scope or an asset not assigned to the token.\n\nNever logs the token itself.";
16
+ readonly inputSchema: {
17
+ response_format: z.ZodDefault<z.ZodNativeEnum<typeof ResponseFormat>>;
18
+ };
19
+ readonly annotations: {
20
+ readonly readOnlyHint: true;
21
+ readonly destructiveHint: false;
22
+ readonly idempotentHint: true;
23
+ readonly openWorldHint: true;
24
+ };
25
+ };
26
+ export declare function handler(input: Input, ctx: ToolContext): Promise<import("../../helpers/format.js").ToolTextResult>;
@@ -0,0 +1,88 @@
1
+ import { z } from "zod";
2
+ import { MetaError } from "../../errors.js";
3
+ import { jsonBlock, ResponseFormat, toolError, toolResult } from "../../helpers/format.js";
4
+ import { responseFormatShape } from "../../helpers/schema.js";
5
+ export const inputSchema = z
6
+ .object({
7
+ ...responseFormatShape,
8
+ })
9
+ .strict();
10
+ export const definition = {
11
+ name: "meta_token_inspect",
12
+ title: "Inspect Meta access token",
13
+ description: `Decodes the configured META_ACCESS_TOKEN via Graph API /debug_token.
14
+
15
+ Returns:
16
+ - app_id + application name
17
+ - token type (system-user / user / page)
18
+ - is_valid
19
+ - expires_at + data_access_expires_at (unix seconds; 0 = never expires)
20
+ - scopes + granular_scopes (per-asset targeting)
21
+
22
+ Use this first when troubleshooting — almost every other tool failure traces back to a missing scope or an asset not assigned to the token.
23
+
24
+ Never logs the token itself.`,
25
+ inputSchema: inputSchema.shape,
26
+ annotations: {
27
+ readOnlyHint: true,
28
+ destructiveHint: false,
29
+ idempotentHint: true,
30
+ openWorldHint: true,
31
+ },
32
+ };
33
+ export async function handler(input, ctx) {
34
+ try {
35
+ const data = await ctx.graph.get({
36
+ path: "debug_token",
37
+ params: { input_token: ctx.config.accessToken },
38
+ noCache: true,
39
+ });
40
+ const debug = data.data ?? {};
41
+ const now = Math.floor(Date.now() / 1000);
42
+ const expiresIn = debug.expires_at && debug.expires_at > 0 ? debug.expires_at - now : null;
43
+ const dataAccessExpiresIn = debug.data_access_expires_at && debug.data_access_expires_at > 0
44
+ ? debug.data_access_expires_at - now
45
+ : null;
46
+ const structured = {
47
+ ...debug,
48
+ expires_in_seconds: expiresIn,
49
+ data_access_expires_in_seconds: dataAccessExpiresIn,
50
+ never_expires: debug.expires_at === 0,
51
+ app_secret_proof_enabled: Boolean(ctx.config.appSecret),
52
+ };
53
+ const text = input.response_format === ResponseFormat.MARKDOWN
54
+ ? renderMarkdown(structured)
55
+ : jsonBlock(structured);
56
+ return toolResult(structured, text);
57
+ }
58
+ catch (err) {
59
+ const e = err instanceof MetaError ? err : new MetaError(err.message);
60
+ return toolError(e.message, e.hint, {
61
+ code: e.code,
62
+ subcode: e.subcode,
63
+ fbtrace_id: e.fbtraceId,
64
+ http_status: e.httpStatus,
65
+ });
66
+ }
67
+ }
68
+ function renderMarkdown(d) {
69
+ const lines = ["# Meta Token Inspection", ""];
70
+ lines.push(`- **Valid**: ${d.is_valid ?? "unknown"}`);
71
+ lines.push(`- **App**: ${d.application ?? "?"} (${d.app_id ?? "?"})`);
72
+ if (d.type)
73
+ lines.push(`- **Type**: ${d.type}`);
74
+ if (d.user_id)
75
+ lines.push(`- **User / System User ID**: ${d.user_id}`);
76
+ lines.push(`- **Expires**: ${d.never_expires ? "never" : d.expires_in_seconds != null ? `in ${d.expires_in_seconds}s` : "unknown"}`);
77
+ lines.push(`- **appsecret_proof**: ${d.app_secret_proof_enabled ? "enabled" : "disabled (recommend enabling)"}`);
78
+ if (d.scopes?.length) {
79
+ lines.push("", "## Scopes", ...d.scopes.map((s) => `- ${s}`));
80
+ }
81
+ if (d.granular_scopes?.length) {
82
+ lines.push("", "## Granular scopes");
83
+ for (const g of d.granular_scopes) {
84
+ lines.push(`- **${g.scope}** → ${g.target_ids?.length ? g.target_ids.join(", ") : "all"}`);
85
+ }
86
+ }
87
+ return lines.join("\n");
88
+ }
@@ -0,0 +1,57 @@
1
+ import { z } from "zod";
2
+ import type { ToolContext } from "../../context.js";
3
+ export declare const inputSchema: z.ZodObject<{
4
+ waba_id: z.ZodString;
5
+ flavor: z.ZodDefault<z.ZodEnum<["analytics", "conversation_analytics"]>>;
6
+ start: z.ZodNumber;
7
+ end: z.ZodNumber;
8
+ granularity: z.ZodDefault<z.ZodEnum<["HALF_HOUR", "DAY", "MONTH"]>>;
9
+ phone_numbers: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
10
+ country_codes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
11
+ conversation_categories: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
12
+ dimensions: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
13
+ }, "strict", z.ZodTypeAny, {
14
+ waba_id: string;
15
+ flavor: "analytics" | "conversation_analytics";
16
+ start: number;
17
+ end: number;
18
+ granularity: "HALF_HOUR" | "DAY" | "MONTH";
19
+ dimensions: string[];
20
+ phone_numbers?: string[] | undefined;
21
+ country_codes?: string[] | undefined;
22
+ conversation_categories?: string[] | undefined;
23
+ }, {
24
+ waba_id: string;
25
+ start: number;
26
+ end: number;
27
+ flavor?: "analytics" | "conversation_analytics" | undefined;
28
+ granularity?: "HALF_HOUR" | "DAY" | "MONTH" | undefined;
29
+ phone_numbers?: string[] | undefined;
30
+ country_codes?: string[] | undefined;
31
+ conversation_categories?: string[] | undefined;
32
+ dimensions?: string[] | undefined;
33
+ }>;
34
+ export type Input = z.infer<typeof inputSchema>;
35
+ export declare const definition: {
36
+ readonly name: "meta_whatsapp_get_analytics";
37
+ readonly title: "Get WhatsApp Business analytics";
38
+ readonly description: "Fetches WABA analytics — either the legacy 'analytics' field (message counts) or the richer 'conversation_analytics' (per-conversation pricing, categories: AUTHENTICATION/MARKETING/SERVICE/UTILITY).\n\nPass start + end as Unix seconds. Use granularity DAY for most reports. Filter by phone_numbers, country_codes, or conversation_categories to narrow.";
39
+ readonly inputSchema: {
40
+ waba_id: z.ZodString;
41
+ flavor: z.ZodDefault<z.ZodEnum<["analytics", "conversation_analytics"]>>;
42
+ start: z.ZodNumber;
43
+ end: z.ZodNumber;
44
+ granularity: z.ZodDefault<z.ZodEnum<["HALF_HOUR", "DAY", "MONTH"]>>;
45
+ phone_numbers: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
46
+ country_codes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
47
+ conversation_categories: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
48
+ dimensions: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
49
+ };
50
+ readonly annotations: {
51
+ readonly readOnlyHint: true;
52
+ readonly destructiveHint: false;
53
+ readonly idempotentHint: true;
54
+ readonly openWorldHint: true;
55
+ };
56
+ };
57
+ export declare function handler(input: Input, ctx: ToolContext): Promise<import("../../helpers/format.js").ToolTextResult>;