@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.
- package/LICENSE +21 -0
- package/README.md +294 -0
- package/dist/config.d.ts +39 -0
- package/dist/config.js +60 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.js +28 -0
- package/dist/context.d.ts +9 -0
- package/dist/context.js +8 -0
- package/dist/errors.d.ts +41 -0
- package/dist/errors.js +120 -0
- package/dist/helpers/cache.d.ts +6 -0
- package/dist/helpers/cache.js +28 -0
- package/dist/helpers/format.d.ts +17 -0
- package/dist/helpers/format.js +28 -0
- package/dist/helpers/graph-client.d.ts +90 -0
- package/dist/helpers/graph-client.js +219 -0
- package/dist/helpers/schema.d.ts +30 -0
- package/dist/helpers/schema.js +69 -0
- package/dist/helpers/validate.d.ts +26 -0
- package/dist/helpers/validate.js +125 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +36 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +18 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +34 -0
- package/dist/tools/ads/get-account.d.ts +29 -0
- package/dist/tools/ads/get-account.js +45 -0
- package/dist/tools/ads/get-creative.d.ts +29 -0
- package/dist/tools/ads/get-creative.js +37 -0
- package/dist/tools/ads/get-insights.d.ts +129 -0
- package/dist/tools/ads/get-insights.js +153 -0
- package/dist/tools/ads/list-accounts.d.ts +54 -0
- package/dist/tools/ads/list-accounts.js +59 -0
- package/dist/tools/ads/list-ads.d.ts +53 -0
- package/dist/tools/ads/list-ads.js +59 -0
- package/dist/tools/ads/list-adsets.d.ts +49 -0
- package/dist/tools/ads/list-adsets.js +54 -0
- package/dist/tools/ads/list-campaigns.d.ts +45 -0
- package/dist/tools/ads/list-campaigns.js +64 -0
- package/dist/tools/ads/list-custom-audiences.d.ts +41 -0
- package/dist/tools/ads/list-custom-audiences.js +41 -0
- package/dist/tools/business/list-assets.d.ts +37 -0
- package/dist/tools/business/list-assets.js +136 -0
- package/dist/tools/business/list-businesses.d.ts +37 -0
- package/dist/tools/business/list-businesses.js +81 -0
- package/dist/tools/business/list-system-users.d.ts +41 -0
- package/dist/tools/business/list-system-users.js +73 -0
- package/dist/tools/catalog/get-diagnostics.d.ts +29 -0
- package/dist/tools/catalog/get-diagnostics.js +26 -0
- package/dist/tools/catalog/list-products.d.ts +45 -0
- package/dist/tools/catalog/list-products.js +49 -0
- package/dist/tools/catalog/list.d.ts +54 -0
- package/dist/tools/catalog/list.js +48 -0
- package/dist/tools/instagram/get-account.d.ts +29 -0
- package/dist/tools/instagram/get-account.js +34 -0
- package/dist/tools/instagram/get-audience-demographics.d.ts +45 -0
- package/dist/tools/instagram/get-audience-demographics.js +58 -0
- package/dist/tools/instagram/get-media-insights.d.ts +29 -0
- package/dist/tools/instagram/get-media-insights.js +49 -0
- package/dist/tools/instagram/list-accounts.d.ts +33 -0
- package/dist/tools/instagram/list-accounts.js +63 -0
- package/dist/tools/instagram/list-media.d.ts +41 -0
- package/dist/tools/instagram/list-media.js +42 -0
- package/dist/tools/meta/graph-read.d.ts +33 -0
- package/dist/tools/meta/graph-read.js +71 -0
- package/dist/tools/overview/business-overview.d.ts +49 -0
- package/dist/tools/overview/business-overview.js +235 -0
- package/dist/tools/overview/content-report.d.ts +57 -0
- package/dist/tools/overview/content-report.js +344 -0
- package/dist/tools/overview/latest-posts-summary.d.ts +41 -0
- package/dist/tools/overview/latest-posts-summary.js +189 -0
- package/dist/tools/pages/get-insights.d.ts +41 -0
- package/dist/tools/pages/get-insights.js +49 -0
- package/dist/tools/pages/get-post-insights.d.ts +33 -0
- package/dist/tools/pages/get-post-insights.js +47 -0
- package/dist/tools/pages/get.d.ts +29 -0
- package/dist/tools/pages/get.js +50 -0
- package/dist/tools/pages/list-posts.d.ts +53 -0
- package/dist/tools/pages/list-posts.js +62 -0
- package/dist/tools/pages/list-reviews.d.ts +41 -0
- package/dist/tools/pages/list-reviews.js +37 -0
- package/dist/tools/pages/list-videos.d.ts +41 -0
- package/dist/tools/pages/list-videos.js +40 -0
- package/dist/tools/pages/list.d.ts +41 -0
- package/dist/tools/pages/list.js +39 -0
- package/dist/tools/pixels/get-stats.d.ts +41 -0
- package/dist/tools/pixels/get-stats.js +34 -0
- package/dist/tools/pixels/list.d.ts +41 -0
- package/dist/tools/pixels/list.js +41 -0
- package/dist/tools/register.d.ts +3 -0
- package/dist/tools/register.js +95 -0
- package/dist/tools/shared.d.ts +45 -0
- package/dist/tools/shared.js +137 -0
- package/dist/tools/token/health.d.ts +17 -0
- package/dist/tools/token/health.js +65 -0
- package/dist/tools/token/inspect.d.ts +26 -0
- package/dist/tools/token/inspect.js +88 -0
- package/dist/tools/whatsapp/get-analytics.d.ts +57 -0
- package/dist/tools/whatsapp/get-analytics.js +66 -0
- package/dist/tools/whatsapp/list-phone-numbers.d.ts +41 -0
- package/dist/tools/whatsapp/list-phone-numbers.js +35 -0
- package/dist/tools/whatsapp/list-templates.d.ts +45 -0
- package/dist/tools/whatsapp/list-templates.js +44 -0
- package/dist/tools/whatsapp/list-wabas.d.ts +54 -0
- package/dist/tools/whatsapp/list-wabas.js +48 -0
- package/dist/types/meta.d.ts +46 -0
- package/dist/types/meta.js +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { metaIdSchema } from "../../helpers/schema.js";
|
|
3
|
+
import { runGetViaInsightsApp } from "../shared.js";
|
|
4
|
+
export const inputSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
media_id: metaIdSchema.describe("IG media ID."),
|
|
7
|
+
metrics: z
|
|
8
|
+
.array(z.string())
|
|
9
|
+
.default([
|
|
10
|
+
// v23-safe set covering Reels (the most common modern IG media type
|
|
11
|
+
// for marketing accounts). 'impressions' is deprecated for IG media
|
|
12
|
+
// in v22+; 'views' replaces it. See the description for media-type
|
|
13
|
+
// / metric compatibility.
|
|
14
|
+
"reach",
|
|
15
|
+
"views",
|
|
16
|
+
"likes",
|
|
17
|
+
"comments",
|
|
18
|
+
"shares",
|
|
19
|
+
"saved",
|
|
20
|
+
"total_interactions",
|
|
21
|
+
])
|
|
22
|
+
.describe("IG media metrics. Defaults are v23-safe for Reels. For IMAGE/CAROUSEL_ALBUM you can drop 'views' and add 'profile_visits'. For STORY use ['views','reach','replies','navigation']. See the tool description for the full media-type/metric compatibility matrix."),
|
|
23
|
+
})
|
|
24
|
+
.strict();
|
|
25
|
+
export const definition = {
|
|
26
|
+
name: "meta_ig_get_media_insights",
|
|
27
|
+
title: "Get Instagram media insights",
|
|
28
|
+
description: `Per-media metrics: reach, views, saves, likes, comments, shares, total_interactions, profile_visits, follows.
|
|
29
|
+
|
|
30
|
+
**Requirements (in order of frequency-of-failure):**
|
|
31
|
+
1. Token has 'instagram_manage_insights' scope.
|
|
32
|
+
2. **The configured app must be approved for Standard Access on \`instagram_manage_insights\` via Meta App Review.** In development tier, Meta returns \`(#10) Application does not have permission for this action\` for the IG insights endpoints — even if the scope is on the token. This applies to media insights AND audience demographics. Track the App Review request and surface this to the operator; this tool is functioning correctly when it returns that error.
|
|
33
|
+
3. The IG account must be Business or Creator (not personal).
|
|
34
|
+
|
|
35
|
+
**Metric / media-type compatibility (post v22 deprecations):**
|
|
36
|
+
- IMAGE / CAROUSEL_ALBUM: reach, saved, likes, comments, shares, total_interactions, profile_visits
|
|
37
|
+
- VIDEO / REELS: views, reach, likes, comments, shares, saved, total_interactions ('impressions' is deprecated, use 'views')
|
|
38
|
+
- STORY: views, reach, replies, navigation, profile_visits, follows (impressions is deprecated; use views)
|
|
39
|
+
|
|
40
|
+
If you see code (#100) "metric not supported for media type", narrow the metrics list to the matching subset above. If you see code (#10), it's almost always App Review (#2 above).`,
|
|
41
|
+
inputSchema: inputSchema.shape,
|
|
42
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
43
|
+
};
|
|
44
|
+
export async function handler(input, ctx) {
|
|
45
|
+
return runGetViaInsightsApp(ctx, {
|
|
46
|
+
path: `${input.media_id}/insights`,
|
|
47
|
+
params: { metric: input.metrics.join(",") },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
}, "strict", z.ZodTypeAny, {
|
|
8
|
+
limit: number;
|
|
9
|
+
auto_paginate: boolean;
|
|
10
|
+
after?: string | undefined;
|
|
11
|
+
}, {
|
|
12
|
+
limit?: number | undefined;
|
|
13
|
+
after?: string | undefined;
|
|
14
|
+
auto_paginate?: boolean | undefined;
|
|
15
|
+
}>;
|
|
16
|
+
export type Input = z.infer<typeof inputSchema>;
|
|
17
|
+
export declare const definition: {
|
|
18
|
+
readonly name: "meta_ig_list_accounts";
|
|
19
|
+
readonly title: "List Instagram Business accounts reachable via Pages";
|
|
20
|
+
readonly description: "Walks the Pages visible to the token (/me/assigned_pages) and, for each, resolves the linked Instagram Business account via 'instagram_business_account'. This is the modern discovery path — the /owned_instagram_accounts business edge is unreliable.\n\nRequires 'instagram_basic' scope on the token and 'Analyze Instagram account' task on each IG.";
|
|
21
|
+
readonly inputSchema: {
|
|
22
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
23
|
+
after: z.ZodOptional<z.ZodString>;
|
|
24
|
+
auto_paginate: z.ZodDefault<z.ZodBoolean>;
|
|
25
|
+
};
|
|
26
|
+
readonly annotations: {
|
|
27
|
+
readonly readOnlyHint: true;
|
|
28
|
+
readonly destructiveHint: false;
|
|
29
|
+
readonly idempotentHint: true;
|
|
30
|
+
readonly openWorldHint: true;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export declare function handler(input: Input, ctx: ToolContext): Promise<import("../../helpers/format.js").ToolTextResult>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { MetaError } from "../../errors.js";
|
|
3
|
+
import { jsonBlock, toolError, toolResult } from "../../helpers/format.js";
|
|
4
|
+
import { paginationShape } from "../../helpers/schema.js";
|
|
5
|
+
export const inputSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
...paginationShape,
|
|
8
|
+
})
|
|
9
|
+
.strict();
|
|
10
|
+
export const definition = {
|
|
11
|
+
name: "meta_ig_list_accounts",
|
|
12
|
+
title: "List Instagram Business accounts reachable via Pages",
|
|
13
|
+
description: `Walks the Pages visible to the token (/me/assigned_pages) and, for each, resolves the linked Instagram Business account via 'instagram_business_account'. This is the modern discovery path — the /owned_instagram_accounts business edge is unreliable.
|
|
14
|
+
|
|
15
|
+
Requires 'instagram_basic' scope on the token and 'Analyze Instagram account' task on each IG.`,
|
|
16
|
+
inputSchema: inputSchema.shape,
|
|
17
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
18
|
+
};
|
|
19
|
+
export async function handler(input, ctx) {
|
|
20
|
+
try {
|
|
21
|
+
// First, list the pages assigned to the token.
|
|
22
|
+
const pagesPage = await ctx.graph.get({
|
|
23
|
+
path: "me/assigned_pages",
|
|
24
|
+
params: { fields: "id,name", limit: input.limit, after: input.after },
|
|
25
|
+
});
|
|
26
|
+
const pages = Array.isArray(pagesPage.data) ? pagesPage.data : [];
|
|
27
|
+
// Resolve IG in parallel.
|
|
28
|
+
const results = await Promise.all(pages.map(async (p) => {
|
|
29
|
+
try {
|
|
30
|
+
const d = await ctx.graph.get({
|
|
31
|
+
path: p.id,
|
|
32
|
+
params: { fields: "id,name,instagram_business_account{id,username,name}" },
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
page_id: p.id,
|
|
36
|
+
page_name: p.name,
|
|
37
|
+
instagram_business_account: d.instagram_business_account ?? null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
const e = err instanceof MetaError ? err : new MetaError(err.message);
|
|
42
|
+
return { page_id: p.id, page_name: p.name, error: e.message, hint: e.hint };
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
45
|
+
const linked = results.filter((r) => "instagram_business_account" in r && r.instagram_business_account);
|
|
46
|
+
const structured = {
|
|
47
|
+
pages_scanned: pages.length,
|
|
48
|
+
ig_accounts_found: linked.length,
|
|
49
|
+
links: results,
|
|
50
|
+
next_after: pagesPage.paging?.cursors?.after,
|
|
51
|
+
};
|
|
52
|
+
return toolResult(structured, jsonBlock(structured));
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
const e = err instanceof MetaError ? err : new MetaError(err.message);
|
|
56
|
+
return toolError(e.message, e.hint, {
|
|
57
|
+
code: e.code,
|
|
58
|
+
subcode: e.subcode,
|
|
59
|
+
fbtrace_id: e.fbtraceId,
|
|
60
|
+
http_status: e.httpStatus,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -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
|
+
ig_user_id: z.ZodString;
|
|
8
|
+
fields: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
9
|
+
}, "strict", z.ZodTypeAny, {
|
|
10
|
+
fields: string[];
|
|
11
|
+
ig_user_id: string;
|
|
12
|
+
limit: number;
|
|
13
|
+
auto_paginate: boolean;
|
|
14
|
+
after?: string | undefined;
|
|
15
|
+
}, {
|
|
16
|
+
ig_user_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_ig_list_media";
|
|
25
|
+
readonly title: "List Instagram media (posts, reels, stories)";
|
|
26
|
+
readonly description: "Lists media posted by an IG Business account. Includes caption, permalink, timestamp, like/comment counts. For per-media metrics (reach, impressions, saves, plays), pass each ID to meta_ig_get_media_insights.";
|
|
27
|
+
readonly inputSchema: {
|
|
28
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
29
|
+
after: z.ZodOptional<z.ZodString>;
|
|
30
|
+
auto_paginate: z.ZodDefault<z.ZodBoolean>;
|
|
31
|
+
ig_user_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,42 @@
|
|
|
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
|
+
ig_user_id: metaIdSchema,
|
|
8
|
+
fields: z
|
|
9
|
+
.array(z.string())
|
|
10
|
+
.default([
|
|
11
|
+
"id",
|
|
12
|
+
"media_type",
|
|
13
|
+
"media_product_type",
|
|
14
|
+
"caption",
|
|
15
|
+
"media_url",
|
|
16
|
+
"thumbnail_url",
|
|
17
|
+
"permalink",
|
|
18
|
+
"timestamp",
|
|
19
|
+
"like_count",
|
|
20
|
+
"comments_count",
|
|
21
|
+
"is_comment_enabled",
|
|
22
|
+
"shortcode",
|
|
23
|
+
"owner",
|
|
24
|
+
])
|
|
25
|
+
.describe("Media fields. For Reels add 'ig_id'; for carousels add 'children{...}'."),
|
|
26
|
+
...paginationShape,
|
|
27
|
+
})
|
|
28
|
+
.strict();
|
|
29
|
+
export const definition = {
|
|
30
|
+
name: "meta_ig_list_media",
|
|
31
|
+
title: "List Instagram media (posts, reels, stories)",
|
|
32
|
+
description: `Lists media posted by an IG Business account. Includes caption, permalink, timestamp, like/comment counts. For per-media metrics (reach, impressions, saves, plays), pass each ID to meta_ig_get_media_insights.`,
|
|
33
|
+
inputSchema: inputSchema.shape,
|
|
34
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
35
|
+
};
|
|
36
|
+
export async function handler(input, ctx) {
|
|
37
|
+
assertAllowed("ig_user", input.ig_user_id, ctx.config);
|
|
38
|
+
return runList(ctx, {
|
|
39
|
+
path: `${input.ig_user_id}/media`,
|
|
40
|
+
params: { fields: input.fields.join(","), limit: input.limit, after: input.after },
|
|
41
|
+
}, { auto_paginate: input.auto_paginate, after: input.after, limit: input.limit }, { ig_user_id: input.ig_user_id });
|
|
42
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ToolContext } from "../../context.js";
|
|
3
|
+
export declare const inputSchema: z.ZodObject<{
|
|
4
|
+
path: z.ZodString;
|
|
5
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
6
|
+
api_version: z.ZodOptional<z.ZodString>;
|
|
7
|
+
}, "strict", z.ZodTypeAny, {
|
|
8
|
+
path: string;
|
|
9
|
+
params?: Record<string, string | number | boolean> | undefined;
|
|
10
|
+
api_version?: string | undefined;
|
|
11
|
+
}, {
|
|
12
|
+
path: string;
|
|
13
|
+
params?: Record<string, string | number | boolean> | undefined;
|
|
14
|
+
api_version?: string | undefined;
|
|
15
|
+
}>;
|
|
16
|
+
export type Input = z.infer<typeof inputSchema>;
|
|
17
|
+
export declare const definition: {
|
|
18
|
+
readonly name: "meta_graph_read";
|
|
19
|
+
readonly title: "Arbitrary read-only Graph API call";
|
|
20
|
+
readonly description: "Escape-hatch for arbitrary GET requests against the Meta Graph API.\n\nThis tool exists so the assistant can reach fields and endpoints that do not yet have a dedicated tool wrapper — without ever sending a write. The underlying HTTP client is hard-wired to GET only; POST/PUT/DELETE are refused before the request leaves the process.\n\nRules:\n- Do NOT include 'access_token' or 'appsecret_proof' in params — they are added automatically.\n- Do NOT prefix the path with the API version.\n- Prefer dedicated tools (meta_business_list_assets, meta_ads_get_insights, …) when they exist. Use this as a last resort.";
|
|
21
|
+
readonly inputSchema: {
|
|
22
|
+
path: z.ZodString;
|
|
23
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
24
|
+
api_version: z.ZodOptional<z.ZodString>;
|
|
25
|
+
};
|
|
26
|
+
readonly annotations: {
|
|
27
|
+
readonly readOnlyHint: true;
|
|
28
|
+
readonly destructiveHint: false;
|
|
29
|
+
readonly idempotentHint: true;
|
|
30
|
+
readonly openWorldHint: true;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export declare function handler(input: Input, ctx: ToolContext): Promise<import("../../helpers/format.js").ToolTextResult>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { MetaError } from "../../errors.js";
|
|
3
|
+
import { jsonBlock, toolError, toolResult } from "../../helpers/format.js";
|
|
4
|
+
const paramsRecord = z
|
|
5
|
+
.record(z.union([z.string(), z.number(), z.boolean()]))
|
|
6
|
+
.optional()
|
|
7
|
+
.describe("Query parameters. `access_token` and `appsecret_proof` are added automatically and must not be supplied here.");
|
|
8
|
+
export const inputSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
path: z
|
|
11
|
+
.string()
|
|
12
|
+
.min(1)
|
|
13
|
+
.describe("Graph API path after the version segment. Examples: 'me/businesses', 'act_1234567890/insights', '100000012345/posts'. Do NOT include a leading 'v23.0/' — the configured API version is added automatically."),
|
|
14
|
+
params: paramsRecord,
|
|
15
|
+
api_version: z
|
|
16
|
+
.string()
|
|
17
|
+
.regex(/^v\d+\.\d+$/)
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Override the Graph API version for this call, e.g. 'v20.0'. Defaults to the configured version."),
|
|
20
|
+
})
|
|
21
|
+
.strict();
|
|
22
|
+
const FORBIDDEN_PARAMS = new Set(["access_token", "appsecret_proof"]);
|
|
23
|
+
export const definition = {
|
|
24
|
+
name: "meta_graph_read",
|
|
25
|
+
title: "Arbitrary read-only Graph API call",
|
|
26
|
+
description: `Escape-hatch for arbitrary GET requests against the Meta Graph API.
|
|
27
|
+
|
|
28
|
+
This tool exists so the assistant can reach fields and endpoints that do not yet have a dedicated tool wrapper — without ever sending a write. The underlying HTTP client is hard-wired to GET only; POST/PUT/DELETE are refused before the request leaves the process.
|
|
29
|
+
|
|
30
|
+
Rules:
|
|
31
|
+
- Do NOT include 'access_token' or 'appsecret_proof' in params — they are added automatically.
|
|
32
|
+
- Do NOT prefix the path with the API version.
|
|
33
|
+
- Prefer dedicated tools (meta_business_list_assets, meta_ads_get_insights, …) when they exist. Use this as a last resort.`,
|
|
34
|
+
inputSchema: inputSchema.shape,
|
|
35
|
+
annotations: {
|
|
36
|
+
readOnlyHint: true,
|
|
37
|
+
destructiveHint: false,
|
|
38
|
+
idempotentHint: true,
|
|
39
|
+
openWorldHint: true,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
export async function handler(input, ctx) {
|
|
43
|
+
for (const k of Object.keys(input.params ?? {})) {
|
|
44
|
+
if (FORBIDDEN_PARAMS.has(k)) {
|
|
45
|
+
return toolError(`Parameter '${k}' must not be supplied — the server adds it automatically.`, "Remove it from the params object and retry.");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const data = await ctx.graph.get({
|
|
50
|
+
path: input.path,
|
|
51
|
+
params: input.params,
|
|
52
|
+
apiVersion: input.api_version,
|
|
53
|
+
});
|
|
54
|
+
const structured = {
|
|
55
|
+
path: input.path,
|
|
56
|
+
api_version: input.api_version ?? ctx.config.apiVersion,
|
|
57
|
+
data,
|
|
58
|
+
};
|
|
59
|
+
return toolResult(structured, jsonBlock(data));
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const e = err instanceof MetaError ? err : new MetaError(err.message);
|
|
63
|
+
return toolError(e.message, e.hint, {
|
|
64
|
+
code: e.code,
|
|
65
|
+
subcode: e.subcode,
|
|
66
|
+
fbtrace_id: e.fbtraceId,
|
|
67
|
+
http_status: e.httpStatus,
|
|
68
|
+
path: input.path,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ToolContext } from "../../context.js";
|
|
3
|
+
export declare const inputSchema: z.ZodObject<{
|
|
4
|
+
business_id: z.ZodOptional<z.ZodString>;
|
|
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
|
+
include_pixels: z.ZodDefault<z.ZodBoolean>;
|
|
7
|
+
include_catalogs: z.ZodDefault<z.ZodBoolean>;
|
|
8
|
+
include_whatsapp: z.ZodDefault<z.ZodBoolean>;
|
|
9
|
+
max_ad_accounts: z.ZodDefault<z.ZodNumber>;
|
|
10
|
+
max_pages: z.ZodDefault<z.ZodNumber>;
|
|
11
|
+
}, "strict", z.ZodTypeAny, {
|
|
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";
|
|
13
|
+
include_pixels: boolean;
|
|
14
|
+
include_catalogs: boolean;
|
|
15
|
+
include_whatsapp: boolean;
|
|
16
|
+
max_ad_accounts: number;
|
|
17
|
+
max_pages: number;
|
|
18
|
+
business_id?: string | undefined;
|
|
19
|
+
}, {
|
|
20
|
+
business_id?: string | undefined;
|
|
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
|
+
include_pixels?: boolean | undefined;
|
|
23
|
+
include_catalogs?: boolean | undefined;
|
|
24
|
+
include_whatsapp?: boolean | undefined;
|
|
25
|
+
max_ad_accounts?: number | undefined;
|
|
26
|
+
max_pages?: number | undefined;
|
|
27
|
+
}>;
|
|
28
|
+
export type Input = z.infer<typeof inputSchema>;
|
|
29
|
+
export declare const definition: {
|
|
30
|
+
readonly name: "meta_business_overview";
|
|
31
|
+
readonly title: "Eagle's-eye snapshot of a business";
|
|
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
|
+
readonly inputSchema: {
|
|
34
|
+
business_id: z.ZodOptional<z.ZodString>;
|
|
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
|
+
include_pixels: z.ZodDefault<z.ZodBoolean>;
|
|
37
|
+
include_catalogs: z.ZodDefault<z.ZodBoolean>;
|
|
38
|
+
include_whatsapp: z.ZodDefault<z.ZodBoolean>;
|
|
39
|
+
max_ad_accounts: z.ZodDefault<z.ZodNumber>;
|
|
40
|
+
max_pages: z.ZodDefault<z.ZodNumber>;
|
|
41
|
+
};
|
|
42
|
+
readonly annotations: {
|
|
43
|
+
readonly readOnlyHint: true;
|
|
44
|
+
readonly destructiveHint: false;
|
|
45
|
+
readonly idempotentHint: true;
|
|
46
|
+
readonly openWorldHint: true;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export declare function handler(input: Input, ctx: ToolContext): Promise<import("../../helpers/format.js").ToolTextResult>;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { assertAllowed } from "../../config.js";
|
|
3
|
+
import { MetaError } from "../../errors.js";
|
|
4
|
+
import { jsonBlock, toolResult } from "../../helpers/format.js";
|
|
5
|
+
import { datePresetSchema, metaIdSchema } from "../../helpers/schema.js";
|
|
6
|
+
export const inputSchema = z
|
|
7
|
+
.object({
|
|
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."),
|
|
11
|
+
date_preset: datePresetSchema
|
|
12
|
+
.default("last_30d")
|
|
13
|
+
.describe("Date window used for ad account and page insights snapshots."),
|
|
14
|
+
include_pixels: z.boolean().default(true),
|
|
15
|
+
include_catalogs: z.boolean().default(true),
|
|
16
|
+
include_whatsapp: z.boolean().default(true),
|
|
17
|
+
max_ad_accounts: z.number().int().min(1).max(50).default(10).describe("Cap on number of ad accounts to fetch insights for. Each is one extra Graph call — tune for rate limits."),
|
|
18
|
+
max_pages: z.number().int().min(1).max(50).default(10).describe("Cap on number of Pages to fetch insights for."),
|
|
19
|
+
})
|
|
20
|
+
.strict();
|
|
21
|
+
export const definition = {
|
|
22
|
+
name: "meta_business_overview",
|
|
23
|
+
title: "Eagle's-eye snapshot of a business",
|
|
24
|
+
description: `One-call consolidated read across the whole Meta surface for a business:
|
|
25
|
+
|
|
26
|
+
- Identity (system user + token app)
|
|
27
|
+
- Assigned Pages + each Page's high-level insights (impressions, engagement, fans) for the requested window
|
|
28
|
+
- Instagram Business accounts linked to those Pages (followers, media_count)
|
|
29
|
+
- Owned + client ad accounts with balance, spend cap, amount spent, and last-window insights (spend, impressions, clicks, CTR, CPC, reach)
|
|
30
|
+
- Pixels with last_fired_time (if include_pixels)
|
|
31
|
+
- Catalogs with product counts (if include_catalogs)
|
|
32
|
+
- WhatsApp Business Accounts + phone numbers (if include_whatsapp)
|
|
33
|
+
|
|
34
|
+
Each 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.`,
|
|
35
|
+
inputSchema: inputSchema.shape,
|
|
36
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
37
|
+
};
|
|
38
|
+
function ok(data) {
|
|
39
|
+
return { ok: true, data };
|
|
40
|
+
}
|
|
41
|
+
function fail(err) {
|
|
42
|
+
const e = err instanceof MetaError ? err : new MetaError(err.message);
|
|
43
|
+
return { ok: false, error: e.message, hint: e.hint };
|
|
44
|
+
}
|
|
45
|
+
export async function handler(input, ctx) {
|
|
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);
|
|
83
|
+
const token = ctx.graph.get({
|
|
84
|
+
path: "debug_token",
|
|
85
|
+
params: { input_token: ctx.config.accessToken },
|
|
86
|
+
noCache: true,
|
|
87
|
+
}).then((r) => ok(r.data)).catch(fail);
|
|
88
|
+
const identity = ctx.graph.get({ path: "me", params: { fields: "id,name" } })
|
|
89
|
+
.then(ok)
|
|
90
|
+
.catch(fail);
|
|
91
|
+
const pages = ctx.graph
|
|
92
|
+
.get({
|
|
93
|
+
path: "me/assigned_pages",
|
|
94
|
+
params: { fields: "id,name,category,fan_count,followers_count", limit: input.max_pages },
|
|
95
|
+
})
|
|
96
|
+
.then(ok)
|
|
97
|
+
.catch(fail);
|
|
98
|
+
const ownedAds = ctx.graph
|
|
99
|
+
.get({
|
|
100
|
+
path: `${businessId}/owned_ad_accounts`,
|
|
101
|
+
params: { fields: "id,account_id,name,currency,account_status,amount_spent,balance,spend_cap", limit: input.max_ad_accounts },
|
|
102
|
+
})
|
|
103
|
+
.then(ok)
|
|
104
|
+
.catch(fail);
|
|
105
|
+
const clientAds = ctx.graph
|
|
106
|
+
.get({
|
|
107
|
+
path: `${businessId}/client_ad_accounts`,
|
|
108
|
+
params: { fields: "id,account_id,name,currency,account_status", limit: input.max_ad_accounts },
|
|
109
|
+
})
|
|
110
|
+
.then(ok)
|
|
111
|
+
.catch(fail);
|
|
112
|
+
const pixels = input.include_pixels
|
|
113
|
+
? ctx.graph
|
|
114
|
+
.get({
|
|
115
|
+
path: `${businessId}/owned_pixels`,
|
|
116
|
+
params: { fields: "id,name,last_fired_time,is_unavailable", limit: 25 },
|
|
117
|
+
})
|
|
118
|
+
.then(ok)
|
|
119
|
+
.catch(fail)
|
|
120
|
+
: Promise.resolve(ok({ skipped: true }));
|
|
121
|
+
const catalogs = input.include_catalogs
|
|
122
|
+
? ctx.graph
|
|
123
|
+
.get({
|
|
124
|
+
path: `${businessId}/owned_product_catalogs`,
|
|
125
|
+
params: { fields: "id,name,vertical,product_count", limit: 25 },
|
|
126
|
+
})
|
|
127
|
+
.then(ok)
|
|
128
|
+
.catch(fail)
|
|
129
|
+
: Promise.resolve(ok({ skipped: true }));
|
|
130
|
+
const wabas = input.include_whatsapp
|
|
131
|
+
? ctx.graph
|
|
132
|
+
.get({
|
|
133
|
+
path: `${businessId}/owned_whatsapp_business_accounts`,
|
|
134
|
+
params: { fields: "id,name,currency,status,business_verification_status", limit: 25 },
|
|
135
|
+
})
|
|
136
|
+
.then(ok)
|
|
137
|
+
.catch(fail)
|
|
138
|
+
: Promise.resolve(ok({ skipped: true }));
|
|
139
|
+
const [identityR, tokenR, pagesR, ownedAdsR, clientAdsR, pixelsR, catalogsR, wabasR] = await Promise.all([
|
|
140
|
+
identity,
|
|
141
|
+
token,
|
|
142
|
+
pages,
|
|
143
|
+
ownedAds,
|
|
144
|
+
clientAds,
|
|
145
|
+
pixels,
|
|
146
|
+
catalogs,
|
|
147
|
+
wabas,
|
|
148
|
+
]);
|
|
149
|
+
// Expand each page with linked IG + a small insights fetch.
|
|
150
|
+
// Page insights require the per-Page access token, so resolve it lazily.
|
|
151
|
+
const pagesExpanded = pagesR.ok && pagesR.data.data
|
|
152
|
+
? await Promise.all(pagesR.data.data.map(async (p) => {
|
|
153
|
+
const igR = await ctx.graph
|
|
154
|
+
.get({
|
|
155
|
+
path: p.id,
|
|
156
|
+
params: { fields: "instagram_business_account{id,username,name,followers_count,media_count}" },
|
|
157
|
+
})
|
|
158
|
+
.then(ok)
|
|
159
|
+
.catch(fail);
|
|
160
|
+
// Resolve Page access token for the insights call. If we can't,
|
|
161
|
+
// record a per-page error rather than blanking the whole report.
|
|
162
|
+
let insightsR;
|
|
163
|
+
try {
|
|
164
|
+
const pageToken = await ctx.graph.getPageAccessToken(p.id);
|
|
165
|
+
insightsR = await ctx.graph
|
|
166
|
+
.get({
|
|
167
|
+
path: `${p.id}/insights`,
|
|
168
|
+
params: {
|
|
169
|
+
// Narrow v23-safe metric set; requires 'read_insights' scope.
|
|
170
|
+
metric: "page_impressions,page_post_engagements,page_fans,page_views_total",
|
|
171
|
+
date_preset: input.date_preset,
|
|
172
|
+
},
|
|
173
|
+
accessTokenOverride: pageToken,
|
|
174
|
+
})
|
|
175
|
+
.then((d) => ok({ data: d.data }))
|
|
176
|
+
.catch(fail);
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
insightsR = fail(err);
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
id: p.id,
|
|
183
|
+
name: p.name,
|
|
184
|
+
category: p.category,
|
|
185
|
+
fan_count: p.fan_count,
|
|
186
|
+
followers_count: p.followers_count,
|
|
187
|
+
instagram: igR.ok ? igR.data.instagram_business_account ?? null : { error: igR.error, hint: igR.hint },
|
|
188
|
+
insights_last_window: insightsR.ok ? insightsR.data.data ?? [] : { error: insightsR.error, hint: insightsR.hint },
|
|
189
|
+
};
|
|
190
|
+
}))
|
|
191
|
+
: [];
|
|
192
|
+
// Same treatment for ad accounts.
|
|
193
|
+
const adAccountsData = [
|
|
194
|
+
...((ownedAdsR.ok && ownedAdsR.data.data) ? ownedAdsR.data.data.map((a) => ({ ...a, scope: "owned" })) : []),
|
|
195
|
+
...((clientAdsR.ok && clientAdsR.data.data) ? clientAdsR.data.data.map((a) => ({ ...a, scope: "client" })) : []),
|
|
196
|
+
].slice(0, input.max_ad_accounts);
|
|
197
|
+
const adAccountsExpanded = await Promise.all(adAccountsData.map(async (a) => {
|
|
198
|
+
const insightsR = await ctx.graph
|
|
199
|
+
.get({
|
|
200
|
+
path: `${a.id}/insights`,
|
|
201
|
+
params: {
|
|
202
|
+
level: "account",
|
|
203
|
+
fields: "spend,impressions,reach,frequency,clicks,ctr,cpc,cpm,actions",
|
|
204
|
+
date_preset: input.date_preset,
|
|
205
|
+
},
|
|
206
|
+
})
|
|
207
|
+
.then(ok)
|
|
208
|
+
.catch(fail);
|
|
209
|
+
return {
|
|
210
|
+
id: a.id,
|
|
211
|
+
account_id: a.account_id,
|
|
212
|
+
name: a.name,
|
|
213
|
+
scope: a.scope,
|
|
214
|
+
currency: a.currency,
|
|
215
|
+
account_status: a.account_status,
|
|
216
|
+
insights_last_window: insightsR.ok ? insightsR.data.data?.[0] ?? null : { error: insightsR.error, hint: insightsR.hint },
|
|
217
|
+
};
|
|
218
|
+
}));
|
|
219
|
+
const structured = {
|
|
220
|
+
business_id: businessId,
|
|
221
|
+
business_id_source: businessIdSource,
|
|
222
|
+
date_preset: input.date_preset,
|
|
223
|
+
generated_at: new Date().toISOString(),
|
|
224
|
+
latency_ms: Date.now() - started,
|
|
225
|
+
identity: identityR,
|
|
226
|
+
token: tokenR,
|
|
227
|
+
pages: pagesExpanded,
|
|
228
|
+
ad_accounts: adAccountsExpanded,
|
|
229
|
+
pixels: pixelsR,
|
|
230
|
+
catalogs: catalogsR,
|
|
231
|
+
whatsapp: wabasR,
|
|
232
|
+
rate_limit_after: ctx.graph.rateLimit,
|
|
233
|
+
};
|
|
234
|
+
return toolResult(structured, jsonBlock(structured));
|
|
235
|
+
}
|