@praise25/meta-mcp-server 0.1.8 → 0.1.11
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 -21
- package/README.md +292 -292
- package/dist/config.d.ts +24 -0
- package/dist/config.js +14 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/helpers/graph-client.d.ts +11 -1
- package/dist/helpers/graph-client.js +24 -16
- package/dist/index.js +0 -0
- package/dist/server.js +21 -1
- package/dist/tools/ads/get-insights.d.ts +1 -1
- package/dist/tools/ads/get-insights.js +12 -10
- package/dist/tools/ads/list-accounts.js +4 -4
- package/dist/tools/business/list-assets.js +10 -10
- package/dist/tools/business/list-businesses.js +4 -4
- package/dist/tools/business/list-system-users.js +4 -4
- package/dist/tools/instagram/get-audience-demographics.d.ts +5 -1
- package/dist/tools/instagram/get-audience-demographics.js +17 -10
- package/dist/tools/instagram/get-media-insights.js +14 -14
- package/dist/tools/instagram/list-accounts.js +2 -2
- package/dist/tools/meta/graph-read.js +7 -7
- package/dist/tools/overview/business-overview.js +10 -10
- 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 +1 -1
- package/dist/tools/overview/latest-posts-summary.js +10 -16
- package/dist/tools/pages/get-insights.js +2 -2
- package/dist/tools/pages/get-post-insights.js +4 -4
- package/dist/tools/pages/list-posts.d.ts +1 -1
- package/dist/tools/pages/list-posts.js +3 -1
- package/dist/tools/register.js +3 -2
- package/dist/tools/shared.d.ts +19 -4
- package/dist/tools/shared.js +56 -0
- package/dist/tools/token/health.js +8 -6
- package/dist/tools/token/inspect.js +11 -11
- package/dist/tools/whatsapp/get-analytics.js +2 -2
- package/package.json +77 -77
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
export interface Config {
|
|
2
2
|
accessToken: string;
|
|
3
3
|
appSecret: string | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Optional secondary "insights app" credentials. Meta's app-type rules
|
|
6
|
+
* forbid combining the Marketing-API use case with the Instagram-content /
|
|
7
|
+
* Pages-everything use cases on a single app. When insights endpoints
|
|
8
|
+
* (IG media/audience insights, Page/post insights) live on a *different*
|
|
9
|
+
* Meta app than the primary (ads) app, set these so those specific calls
|
|
10
|
+
* route through the second app's token + secret. Leave unset to use the
|
|
11
|
+
* primary token for everything (single-app deployments).
|
|
12
|
+
*/
|
|
13
|
+
insightsAccessToken: string | undefined;
|
|
14
|
+
insightsAppSecret: string | undefined;
|
|
4
15
|
apiVersion: string;
|
|
5
16
|
httpTimeoutMs: number;
|
|
6
17
|
cacheTtlSeconds: number;
|
|
@@ -11,5 +22,18 @@ export interface Config {
|
|
|
11
22
|
allowedIgUserIds: Set<string> | null;
|
|
12
23
|
logLevel: string;
|
|
13
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Credentials for a single Meta app: an access token plus the app secret used
|
|
27
|
+
* to compute the request's `appsecret_proof`. Returned by `insightsCreds()`.
|
|
28
|
+
*/
|
|
29
|
+
export interface AppCreds {
|
|
30
|
+
token: string;
|
|
31
|
+
appSecret: string | undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Returns the secondary insights-app credentials when configured, otherwise
|
|
35
|
+
* null. Insight tools call this; null means "fall back to the primary token".
|
|
36
|
+
*/
|
|
37
|
+
export declare function insightsCreds(config: Config): AppCreds | null;
|
|
14
38
|
export declare function loadConfig(): Config;
|
|
15
39
|
export declare function assertAllowed(kind: "business" | "ad_account" | "page" | "ig_user", id: string, config: Config): void;
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { DEFAULT_API_VERSION } from "./constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* Returns the secondary insights-app credentials when configured, otherwise
|
|
4
|
+
* null. Insight tools call this; null means "fall back to the primary token".
|
|
5
|
+
*/
|
|
6
|
+
export function insightsCreds(config) {
|
|
7
|
+
if (!config.insightsAccessToken)
|
|
8
|
+
return null;
|
|
9
|
+
return {
|
|
10
|
+
token: config.insightsAccessToken,
|
|
11
|
+
appSecret: config.insightsAppSecret,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
2
14
|
function parseAllowlist(raw) {
|
|
3
15
|
if (!raw || !raw.trim())
|
|
4
16
|
return null;
|
|
@@ -22,6 +34,8 @@ export function loadConfig() {
|
|
|
22
34
|
return {
|
|
23
35
|
accessToken,
|
|
24
36
|
appSecret: process.env.META_APP_SECRET?.trim() || undefined,
|
|
37
|
+
insightsAccessToken: process.env.META_INSIGHTS_ACCESS_TOKEN?.trim() || undefined,
|
|
38
|
+
insightsAppSecret: process.env.META_INSIGHTS_APP_SECRET?.trim() || undefined,
|
|
25
39
|
apiVersion: process.env.META_API_VERSION?.trim() || DEFAULT_API_VERSION,
|
|
26
40
|
httpTimeoutMs: parseNumber(process.env.META_HTTP_TIMEOUT_MS, 30_000),
|
|
27
41
|
cacheTtlSeconds: parseNumber(process.env.META_CACHE_TTL_SECONDS, 120),
|
package/dist/constants.d.ts
CHANGED
|
@@ -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.
|
|
7
|
+
export declare const SERVER_VERSION = "0.1.11";
|
|
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.
|
|
7
|
+
export const SERVER_VERSION = "0.1.11";
|
|
8
8
|
export const META_ERROR_CODES = {
|
|
9
9
|
UNKNOWN: 1,
|
|
10
10
|
SERVICE_TEMPORARILY_UNAVAILABLE: 2,
|
|
@@ -19,6 +19,13 @@ export interface GraphGetOptions {
|
|
|
19
19
|
* binding to the app remains intact.
|
|
20
20
|
*/
|
|
21
21
|
accessTokenOverride?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Override the app secret used to compute `appsecret_proof` for this call.
|
|
24
|
+
* Required when `accessTokenOverride` is a token issued by a *different*
|
|
25
|
+
* Meta app (e.g. the secondary "insights app"), because the proof must be
|
|
26
|
+
* HMAC(token, that app's secret). Defaults to the primary app secret.
|
|
27
|
+
*/
|
|
28
|
+
appSecretOverride?: string;
|
|
22
29
|
}
|
|
23
30
|
export interface GraphPagedResponse<T = unknown> {
|
|
24
31
|
data: T[];
|
|
@@ -60,7 +67,10 @@ export declare class GraphClient {
|
|
|
60
67
|
* Requires the configured system user to be assigned to the Page with
|
|
61
68
|
* at least the "View performance" or "Analyze Page" task.
|
|
62
69
|
*/
|
|
63
|
-
getPageAccessToken(pageId: string
|
|
70
|
+
getPageAccessToken(pageId: string, creds?: {
|
|
71
|
+
token: string;
|
|
72
|
+
appSecret: string | undefined;
|
|
73
|
+
}): Promise<string>;
|
|
64
74
|
get<T = GraphPagedResponse>(opts: GraphGetOptions): Promise<T>;
|
|
65
75
|
/**
|
|
66
76
|
* Follow `paging.next` cursors and concatenate `data` arrays up to maxPages.
|
|
@@ -51,10 +51,13 @@ export class GraphClient {
|
|
|
51
51
|
* Requires the configured system user to be assigned to the Page with
|
|
52
52
|
* at least the "View performance" or "Analyze Page" task.
|
|
53
53
|
*/
|
|
54
|
-
async getPageAccessToken(pageId) {
|
|
54
|
+
async getPageAccessToken(pageId, creds) {
|
|
55
55
|
if (!pageId)
|
|
56
56
|
throw new MetaError("getPageAccessToken: pageId is required");
|
|
57
|
-
|
|
57
|
+
// Cache key namespaces by the issuing app so a Page token derived from the
|
|
58
|
+
// primary app and one derived from the secondary insights app never collide.
|
|
59
|
+
const cacheKey = creds ? `insights:${pageId}` : pageId;
|
|
60
|
+
const cached = this.pageTokens.get(cacheKey);
|
|
58
61
|
if (cached)
|
|
59
62
|
return cached;
|
|
60
63
|
const resp = await this.get({
|
|
@@ -63,20 +66,22 @@ export class GraphClient {
|
|
|
63
66
|
// Don't cache via the URL+params LRU because the value is sensitive and
|
|
64
67
|
// we want to keep the per-page Map as the single source of truth.
|
|
65
68
|
noCache: true,
|
|
69
|
+
accessTokenOverride: creds?.token,
|
|
70
|
+
appSecretOverride: creds?.appSecret,
|
|
66
71
|
});
|
|
67
72
|
if (!resp.access_token) {
|
|
68
73
|
throw new MetaError(`Page ${pageId} did not return an access_token. The system user may not be assigned to this Page or lacks the required task.`, {
|
|
69
74
|
hint: "In Business Settings → System Users → AI_Insights_Reader → Add Assets, assign this Page with at least 'View performance' or 'Analyze Page' tasks.",
|
|
70
75
|
});
|
|
71
76
|
}
|
|
72
|
-
this.pageTokens.set(
|
|
77
|
+
this.pageTokens.set(cacheKey, resp.access_token);
|
|
73
78
|
return resp.access_token;
|
|
74
79
|
}
|
|
75
80
|
async get(opts) {
|
|
76
81
|
const version = opts.apiVersion ?? this.config.apiVersion;
|
|
77
82
|
const path = opts.path.replace(/^\/+/, "");
|
|
78
83
|
const url = `/${version}/${path}`;
|
|
79
|
-
const params = this.buildParams(opts.params, opts.accessTokenOverride);
|
|
84
|
+
const params = this.buildParams(opts.params, opts.accessTokenOverride, opts.appSecretOverride);
|
|
80
85
|
const cacheKey = this.cacheKeyFor(url, params);
|
|
81
86
|
if (!opts.noCache) {
|
|
82
87
|
const cached = this.cache.get(cacheKey);
|
|
@@ -106,11 +111,12 @@ export class GraphClient {
|
|
|
106
111
|
collected.push(...page.data);
|
|
107
112
|
nextAfter = page.paging?.cursors?.after;
|
|
108
113
|
if (page.paging?.next && pages < cap) {
|
|
109
|
-
// Propagate the original
|
|
110
|
-
// optsFromNextUrl strips
|
|
111
|
-
// (if any) must continue to be applied.
|
|
114
|
+
// Propagate the original token + app-secret overrides to subsequent
|
|
115
|
+
// pages — optsFromNextUrl strips credentials from the URL but the
|
|
116
|
+
// overrides (if any) must continue to be applied.
|
|
112
117
|
const next = this.optsFromNextUrl(page.paging.next);
|
|
113
118
|
next.accessTokenOverride = opts.accessTokenOverride;
|
|
119
|
+
next.appSecretOverride = opts.appSecretOverride;
|
|
114
120
|
currentOpts = next;
|
|
115
121
|
}
|
|
116
122
|
else {
|
|
@@ -132,7 +138,7 @@ export class GraphClient {
|
|
|
132
138
|
}
|
|
133
139
|
return { path, params, apiVersion: version };
|
|
134
140
|
}
|
|
135
|
-
buildParams(params, accessTokenOverride) {
|
|
141
|
+
buildParams(params, accessTokenOverride, appSecretOverride) {
|
|
136
142
|
const out = {};
|
|
137
143
|
for (const [k, v] of Object.entries(params ?? {})) {
|
|
138
144
|
if (v === undefined || v === null)
|
|
@@ -140,19 +146,21 @@ export class GraphClient {
|
|
|
140
146
|
out[k] = v;
|
|
141
147
|
}
|
|
142
148
|
const token = accessTokenOverride ?? this.config.accessToken;
|
|
149
|
+
// The app secret must match the app that ISSUED the token. For a
|
|
150
|
+
// secondary-app token, the caller passes appSecretOverride; otherwise we
|
|
151
|
+
// fall back to the primary app secret.
|
|
152
|
+
const appSecret = appSecretOverride ?? this.config.appSecret;
|
|
143
153
|
out.access_token = token;
|
|
144
|
-
if (
|
|
145
|
-
out.appsecret_proof = this.appsecretProof(token);
|
|
154
|
+
if (appSecret) {
|
|
155
|
+
out.appsecret_proof = this.appsecretProof(token, appSecret);
|
|
146
156
|
}
|
|
147
157
|
return out;
|
|
148
158
|
}
|
|
149
|
-
appsecretProof(token) {
|
|
159
|
+
appsecretProof(token, appSecret) {
|
|
150
160
|
// appsecret_proof = HMAC-SHA256(access_token, app_secret).
|
|
151
|
-
// Recompute against whichever token is in use
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
.update(token ?? this.config.accessToken)
|
|
155
|
-
.digest("hex");
|
|
161
|
+
// Recompute against whichever (token, app-secret) pair is in use —
|
|
162
|
+
// primary system-user token, Page token, or a secondary-app token.
|
|
163
|
+
return crypto.createHmac("sha256", appSecret).update(token).digest("hex");
|
|
156
164
|
}
|
|
157
165
|
cacheKeyFor(url, params) {
|
|
158
166
|
const { access_token: _at, appsecret_proof: _ap, ...rest } = params;
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/server.js
CHANGED
|
@@ -2,11 +2,31 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { createContext } from "./context.js";
|
|
3
3
|
import { SERVER_NAME, SERVER_VERSION } from "./constants.js";
|
|
4
4
|
import { registerTools } from "./tools/register.js";
|
|
5
|
+
/**
|
|
6
|
+
* Server-level routing guidance surfaced to MCP clients via the SDK's
|
|
7
|
+
* `instructions` field. Gateways that inject server instructions into the
|
|
8
|
+
* model's system prompt use this to pick the right tool without the user
|
|
9
|
+
* having to fine-tune their phrasing. Keep it short, imperative, and focused
|
|
10
|
+
* on the question-classes users actually ask.
|
|
11
|
+
*/
|
|
12
|
+
const SERVER_INSTRUCTIONS = `This server exposes READ-ONLY Meta Business Manager analytics (Facebook Pages, Instagram, ads, pixels, catalog, WhatsApp).
|
|
13
|
+
|
|
14
|
+
TOOL ROUTING — pick the broadest matching tool and CALL IT. Never tell the user to rephrase, and never ask for IDs the server can discover itself:
|
|
15
|
+
|
|
16
|
+
- "How is my marketing doing / give me an overview / snapshot" → meta_business_overview (no business_id needed; it auto-discovers).
|
|
17
|
+
- "How did my LATEST / most recent post do" → meta_latest_posts_summary.
|
|
18
|
+
- "How many posts in the last week / this month, by type, performance comparison, content report, trend" → meta_content_report (takes a date window).
|
|
19
|
+
- "Audience demographics / age / gender / country / city breakdown" → meta_ig_get_audience_demographics.
|
|
20
|
+
- "Ad spend / campaign performance / CTR / CPC / ROAS / which ad/campaign" → meta_ads_get_insights (level=account|campaign|adset|ad, with breakdowns).
|
|
21
|
+
- "What assets / pages / accounts do I have" → meta_business_list_assets.
|
|
22
|
+
- Unsure which asset ID to use → call a discovery/report tool first (they auto-discover); do NOT guess placeholder IDs.
|
|
23
|
+
|
|
24
|
+
If a tool returns a (#10)/(#200) permission error, relay its 'hint' to the user verbatim — it names the exact Meta App Review or scope action needed. Prefer the report/overview tools (one call) over chaining several low-level tools.`;
|
|
5
25
|
export function buildServer(config, logger) {
|
|
6
26
|
const server = new McpServer({
|
|
7
27
|
name: SERVER_NAME,
|
|
8
28
|
version: SERVER_VERSION,
|
|
9
|
-
});
|
|
29
|
+
}, { instructions: SERVER_INSTRUCTIONS });
|
|
10
30
|
const ctx = createContext(config, logger);
|
|
11
31
|
const toolNames = registerTools(server, ctx);
|
|
12
32
|
logger.info({ tools: toolNames, count: toolNames.length }, "tools registered");
|
|
@@ -82,7 +82,7 @@ export type Input = z.infer<typeof inputSchema>;
|
|
|
82
82
|
export declare const definition: {
|
|
83
83
|
readonly name: "meta_ads_get_insights";
|
|
84
84
|
readonly title: "Get Marketing API insights (the workhorse)";
|
|
85
|
-
readonly description: "Fetch Marketing API insights for any ad object: ad account ('act_<id>'), campaign, ad set, or ad.\n\nReturns spend, impressions, clicks, CPC/CPM/CTR, reach, frequency, actions (conversions), and video metrics — with optional demographic / placement / device breakdowns.\n\nTips for the AI consuming this:\n- Default date_preset is last_30d. Override via date_preset or time_range.\n- Set level to 'ad' and breakdowns=['publisher_platform','platform_position'] to see which placements convert.\n- Use action_breakdowns=['action_type'] to split purchases vs. add_to_cart vs. lead.\n- If you get code 613 or 17, you hit rate limits — narrow the date range or drop breakdowns.\n\nRequires 'ads_read' token scope.";
|
|
85
|
+
readonly description: "Fetch Marketing API insights for any ad object: ad account ('act_<id>'), campaign, ad set, or ad.\n\nUSE FOR ad/paid questions: \"how much did I spend\", \"ad spend last 7/30 days\", \"campaign performance\", \"which campaign/ad performed best\", \"CTR / CPC / CPM / ROAS / cost per result\", \"conversions from ads\", \"ad performance comparison\", \"spend by placement / age / gender / country\". This is PAID-ads analytics. For organic post/content performance use meta_content_report; for the latest organic post use meta_latest_posts_summary.\n\nReturns spend, impressions, clicks, CPC/CPM/CTR, reach, frequency, actions (conversions), and video metrics — with optional demographic / placement / device breakdowns.\n\nTips for the AI consuming this:\n- Default date_preset is last_30d. Override via date_preset or time_range.\n- Set level to 'ad' and breakdowns=['publisher_platform','platform_position'] to see which placements convert.\n- Use action_breakdowns=['action_type'] to split purchases vs. add_to_cart vs. lead.\n- If you get code 613 or 17, you hit rate limits — narrow the date range or drop breakdowns.\n\nRequires 'ads_read' token scope.";
|
|
86
86
|
readonly inputSchema: {
|
|
87
87
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
88
88
|
after: z.ZodOptional<z.ZodString>;
|
|
@@ -101,16 +101,18 @@ export const inputSchema = z
|
|
|
101
101
|
export const definition = {
|
|
102
102
|
name: "meta_ads_get_insights",
|
|
103
103
|
title: "Get Marketing API insights (the workhorse)",
|
|
104
|
-
description: `Fetch Marketing API insights for any ad object: ad account ('act_<id>'), campaign, ad set, or ad.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
|
|
104
|
+
description: `Fetch Marketing API insights for any ad object: ad account ('act_<id>'), campaign, ad set, or ad.
|
|
105
|
+
|
|
106
|
+
USE FOR ad/paid questions: "how much did I spend", "ad spend last 7/30 days", "campaign performance", "which campaign/ad performed best", "CTR / CPC / CPM / ROAS / cost per result", "conversions from ads", "ad performance comparison", "spend by placement / age / gender / country". This is PAID-ads analytics. For organic post/content performance use meta_content_report; for the latest organic post use meta_latest_posts_summary.
|
|
107
|
+
|
|
108
|
+
Returns spend, impressions, clicks, CPC/CPM/CTR, reach, frequency, actions (conversions), and video metrics — with optional demographic / placement / device breakdowns.
|
|
109
|
+
|
|
110
|
+
Tips for the AI consuming this:
|
|
111
|
+
- Default date_preset is last_30d. Override via date_preset or time_range.
|
|
112
|
+
- Set level to 'ad' and breakdowns=['publisher_platform','platform_position'] to see which placements convert.
|
|
113
|
+
- Use action_breakdowns=['action_type'] to split purchases vs. add_to_cart vs. lead.
|
|
114
|
+
- If you get code 613 or 17, you hit rate limits — narrow the date range or drop breakdowns.
|
|
115
|
+
|
|
114
116
|
Requires 'ads_read' token scope.`,
|
|
115
117
|
inputSchema: inputSchema.shape,
|
|
116
118
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
@@ -31,10 +31,10 @@ export const inputSchema = z
|
|
|
31
31
|
export const definition = {
|
|
32
32
|
name: "meta_ads_list_accounts",
|
|
33
33
|
title: "List ad accounts under a business",
|
|
34
|
-
description: `Lists ad accounts the business owns (owned_ad_accounts) and/or has access to (client_ad_accounts).
|
|
35
|
-
|
|
36
|
-
Needs token scope 'ads_read' and the system user must have 'View performance' task on each account.
|
|
37
|
-
|
|
34
|
+
description: `Lists ad accounts the business owns (owned_ad_accounts) and/or has access to (client_ad_accounts).
|
|
35
|
+
|
|
36
|
+
Needs token scope 'ads_read' and the system user must have 'View performance' task on each account.
|
|
37
|
+
|
|
38
38
|
Returns account_id (numeric), currency, timezone, status, balance, spend cap. Use 'act_<account_id>' when feeding downstream tools like meta_ads_get_insights.`,
|
|
39
39
|
inputSchema: inputSchema.shape,
|
|
40
40
|
annotations: {
|
|
@@ -34,16 +34,16 @@ export const inputSchema = z
|
|
|
34
34
|
export const definition = {
|
|
35
35
|
name: "meta_business_list_assets",
|
|
36
36
|
title: "List assets assigned to a Business Manager",
|
|
37
|
-
description: `For a given business_id, enumerates the business assets the token can see in one call:
|
|
38
|
-
- Ad accounts (owned + optionally client)
|
|
39
|
-
- Facebook Pages (owned + optionally client)
|
|
40
|
-
- Instagram Business accounts
|
|
41
|
-
- Meta Pixels
|
|
42
|
-
- Product catalogs
|
|
43
|
-
- WhatsApp Business accounts (WABAs)
|
|
44
|
-
|
|
45
|
-
Each section is fetched as a separate Graph call. Failures on individual sections do not abort the others — the response reports per-section success/error so the assistant can work around partial failures.
|
|
46
|
-
|
|
37
|
+
description: `For a given business_id, enumerates the business assets the token can see in one call:
|
|
38
|
+
- Ad accounts (owned + optionally client)
|
|
39
|
+
- Facebook Pages (owned + optionally client)
|
|
40
|
+
- Instagram Business accounts
|
|
41
|
+
- Meta Pixels
|
|
42
|
+
- Product catalogs
|
|
43
|
+
- WhatsApp Business accounts (WABAs)
|
|
44
|
+
|
|
45
|
+
Each section is fetched as a separate Graph call. Failures on individual sections do not abort the others — the response reports per-section success/error so the assistant can work around partial failures.
|
|
46
|
+
|
|
47
47
|
Use this to discover IDs for downstream insight tools.`,
|
|
48
48
|
inputSchema: inputSchema.shape,
|
|
49
49
|
annotations: {
|
|
@@ -23,10 +23,10 @@ export const inputSchema = z
|
|
|
23
23
|
export const definition = {
|
|
24
24
|
name: "meta_business_list",
|
|
25
25
|
title: "List Meta businesses visible to the token",
|
|
26
|
-
description: `Lists every Business Manager the configured access token can see — via GET /me/businesses.
|
|
27
|
-
|
|
28
|
-
Typical usage: call this once at the start of a session to discover available business IDs, then pass the chosen ID to meta_business_list_assets / meta_business_list_system_users.
|
|
29
|
-
|
|
26
|
+
description: `Lists every Business Manager the configured access token can see — via GET /me/businesses.
|
|
27
|
+
|
|
28
|
+
Typical usage: call this once at the start of a session to discover available business IDs, then pass the chosen ID to meta_business_list_assets / meta_business_list_system_users.
|
|
29
|
+
|
|
30
30
|
Respects META_ALLOWED_BUSINESS_IDS: if set, filters the response to that allowlist.`,
|
|
31
31
|
inputSchema: inputSchema.shape,
|
|
32
32
|
annotations: {
|
|
@@ -16,10 +16,10 @@ export const inputSchema = z
|
|
|
16
16
|
export const definition = {
|
|
17
17
|
name: "meta_business_list_system_users",
|
|
18
18
|
title: "List system users attached to a business",
|
|
19
|
-
description: `Lists the system-user (server/software) identities under a Business Manager.
|
|
20
|
-
|
|
21
|
-
System users are non-human identities used for API access. This tool helps diagnose which identity owns the token in use (cross-reference with meta_token_inspect).
|
|
22
|
-
|
|
19
|
+
description: `Lists the system-user (server/software) identities under a Business Manager.
|
|
20
|
+
|
|
21
|
+
System users are non-human identities used for API access. This tool helps diagnose which identity owns the token in use (cross-reference with meta_token_inspect).
|
|
22
|
+
|
|
23
23
|
Reads /{business_id}/system_users.`,
|
|
24
24
|
inputSchema: inputSchema.shape,
|
|
25
25
|
annotations: {
|
|
@@ -5,16 +5,19 @@ export declare const inputSchema: z.ZodObject<{
|
|
|
5
5
|
metric: z.ZodDefault<z.ZodEnum<["follower_demographics", "engaged_audience_demographics", "reached_audience_demographics"]>>;
|
|
6
6
|
breakdown: z.ZodDefault<z.ZodEnum<["age", "gender", "country", "city", "audience_city"]>>;
|
|
7
7
|
metric_type: z.ZodDefault<z.ZodEnum<["total_value", "time_series"]>>;
|
|
8
|
+
period: z.ZodDefault<z.ZodEnum<["lifetime", "day"]>>;
|
|
8
9
|
timeframe: z.ZodDefault<z.ZodEnum<["last_14_days", "last_30_days", "last_90_days", "prev_month", "this_month", "this_week"]>>;
|
|
9
10
|
}, "strict", z.ZodTypeAny, {
|
|
10
11
|
ig_user_id: string;
|
|
11
12
|
metric: "follower_demographics" | "engaged_audience_demographics" | "reached_audience_demographics";
|
|
13
|
+
period: "day" | "lifetime";
|
|
12
14
|
breakdown: "age" | "gender" | "country" | "city" | "audience_city";
|
|
13
15
|
metric_type: "total_value" | "time_series";
|
|
14
16
|
timeframe: "this_month" | "last_14_days" | "last_30_days" | "last_90_days" | "prev_month" | "this_week";
|
|
15
17
|
}, {
|
|
16
18
|
ig_user_id: string;
|
|
17
19
|
metric?: "follower_demographics" | "engaged_audience_demographics" | "reached_audience_demographics" | undefined;
|
|
20
|
+
period?: "day" | "lifetime" | undefined;
|
|
18
21
|
breakdown?: "age" | "gender" | "country" | "city" | "audience_city" | undefined;
|
|
19
22
|
metric_type?: "total_value" | "time_series" | undefined;
|
|
20
23
|
timeframe?: "this_month" | "last_14_days" | "last_30_days" | "last_90_days" | "prev_month" | "this_week" | undefined;
|
|
@@ -23,12 +26,13 @@ export type Input = z.infer<typeof inputSchema>;
|
|
|
23
26
|
export declare const definition: {
|
|
24
27
|
readonly name: "meta_ig_get_audience_demographics";
|
|
25
28
|
readonly title: "Get IG audience demographics";
|
|
26
|
-
readonly description: "Reads demographic breakdown of an IG Business account's followers, engaged audience, or reached audience. Use breakdown='age'|'gender'|'country'|'city' to slice.\n\
|
|
29
|
+
readonly description: "Reads demographic breakdown of an IG Business account's followers, engaged audience, or reached audience. Use breakdown='age'|'gender'|'country'|'city' to slice.\n\nUSE FOR: \"audience demographics\", \"age / gender / country / city breakdown of my followers\", \"who follows me\", \"audience profile\".\n\nRequirements:\n1. Token has 'instagram_manage_insights' scope (the insights app / META_INSIGHTS_* token).\n2. The IG Business account must have ≥ 100 followers — below that Meta returns empty for privacy.\n3. Meta requires period='lifetime' for demographics metrics (sent automatically by this tool).\n\nIf you see '(#10) Application does not have permission', the configured app lacks instagram_manage_insights — relay the hint. If you see '(#100) period is required', upgrade the server (fixed in v0.1.11+).";
|
|
27
30
|
readonly inputSchema: {
|
|
28
31
|
ig_user_id: z.ZodString;
|
|
29
32
|
metric: z.ZodDefault<z.ZodEnum<["follower_demographics", "engaged_audience_demographics", "reached_audience_demographics"]>>;
|
|
30
33
|
breakdown: z.ZodDefault<z.ZodEnum<["age", "gender", "country", "city", "audience_city"]>>;
|
|
31
34
|
metric_type: z.ZodDefault<z.ZodEnum<["total_value", "time_series"]>>;
|
|
35
|
+
period: z.ZodDefault<z.ZodEnum<["lifetime", "day"]>>;
|
|
32
36
|
timeframe: z.ZodDefault<z.ZodEnum<["last_14_days", "last_30_days", "last_90_days", "prev_month", "this_month", "this_week"]>>;
|
|
33
37
|
};
|
|
34
38
|
readonly annotations: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { assertAllowed } from "../../config.js";
|
|
3
3
|
import { metaIdSchema } from "../../helpers/schema.js";
|
|
4
|
-
import {
|
|
4
|
+
import { runGetViaInsightsApp } from "../shared.js";
|
|
5
5
|
const METRIC_TYPE = z.enum(["total_value", "time_series"]);
|
|
6
6
|
const BREAKDOWN = z.enum(["age", "gender", "country", "city", "audience_city"]);
|
|
7
7
|
export const inputSchema = z
|
|
@@ -17,6 +17,10 @@ export const inputSchema = z
|
|
|
17
17
|
.describe("Which audience cohort."),
|
|
18
18
|
breakdown: BREAKDOWN.default("age").describe("Demographic dimension."),
|
|
19
19
|
metric_type: METRIC_TYPE.default("total_value"),
|
|
20
|
+
period: z
|
|
21
|
+
.enum(["lifetime", "day"])
|
|
22
|
+
.default("lifetime")
|
|
23
|
+
.describe("Required by Meta for demographics metrics — almost always 'lifetime'. Omitting it triggers '(#100) The parameter period is required'."),
|
|
20
24
|
timeframe: z
|
|
21
25
|
.enum(["last_14_days", "last_30_days", "last_90_days", "prev_month", "this_month", "this_week"])
|
|
22
26
|
.default("last_30_days")
|
|
@@ -26,23 +30,26 @@ export const inputSchema = z
|
|
|
26
30
|
export const definition = {
|
|
27
31
|
name: "meta_ig_get_audience_demographics",
|
|
28
32
|
title: "Get IG audience demographics",
|
|
29
|
-
description: `Reads demographic breakdown of an IG Business account's followers, engaged audience, or reached audience. Use breakdown='age'|'gender'|'country'|'city' to slice.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
description: `Reads demographic breakdown of an IG Business account's followers, engaged audience, or reached audience. Use breakdown='age'|'gender'|'country'|'city' to slice.
|
|
34
|
+
|
|
35
|
+
USE FOR: "audience demographics", "age / gender / country / city breakdown of my followers", "who follows me", "audience profile".
|
|
36
|
+
|
|
37
|
+
Requirements:
|
|
38
|
+
1. Token has 'instagram_manage_insights' scope (the insights app / META_INSIGHTS_* token).
|
|
39
|
+
2. The IG Business account must have ≥ 100 followers — below that Meta returns empty for privacy.
|
|
40
|
+
3. Meta requires period='lifetime' for demographics metrics (sent automatically by this tool).
|
|
41
|
+
|
|
42
|
+
If you see '(#10) Application does not have permission', the configured app lacks instagram_manage_insights — relay the hint. If you see '(#100) period is required', upgrade the server (fixed in v0.1.11+).`,
|
|
37
43
|
inputSchema: inputSchema.shape,
|
|
38
44
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
39
45
|
};
|
|
40
46
|
export async function handler(input, ctx) {
|
|
41
47
|
assertAllowed("ig_user", input.ig_user_id, ctx.config);
|
|
42
|
-
return
|
|
48
|
+
return runGetViaInsightsApp(ctx, {
|
|
43
49
|
path: `${input.ig_user_id}/insights`,
|
|
44
50
|
params: {
|
|
45
51
|
metric: input.metric,
|
|
52
|
+
period: input.period,
|
|
46
53
|
breakdown: input.breakdown,
|
|
47
54
|
metric_type: input.metric_type,
|
|
48
55
|
timeframe: input.timeframe,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { metaIdSchema } from "../../helpers/schema.js";
|
|
3
|
-
import {
|
|
3
|
+
import { runGetViaInsightsApp } from "../shared.js";
|
|
4
4
|
export const inputSchema = z
|
|
5
5
|
.object({
|
|
6
6
|
media_id: metaIdSchema.describe("IG media ID."),
|
|
@@ -25,24 +25,24 @@ export const inputSchema = z
|
|
|
25
25
|
export const definition = {
|
|
26
26
|
name: "meta_ig_get_media_insights",
|
|
27
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
|
-
|
|
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
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
41
|
inputSchema: inputSchema.shape,
|
|
42
42
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
43
43
|
};
|
|
44
44
|
export async function handler(input, ctx) {
|
|
45
|
-
return
|
|
45
|
+
return runGetViaInsightsApp(ctx, {
|
|
46
46
|
path: `${input.media_id}/insights`,
|
|
47
47
|
params: { metric: input.metrics.join(",") },
|
|
48
48
|
});
|
|
@@ -10,8 +10,8 @@ export const inputSchema = z
|
|
|
10
10
|
export const definition = {
|
|
11
11
|
name: "meta_ig_list_accounts",
|
|
12
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
|
-
|
|
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
15
|
Requires 'instagram_basic' scope on the token and 'Analyze Instagram account' task on each IG.`,
|
|
16
16
|
inputSchema: inputSchema.shape,
|
|
17
17
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
@@ -23,13 +23,13 @@ const FORBIDDEN_PARAMS = new Set(["access_token", "appsecret_proof"]);
|
|
|
23
23
|
export const definition = {
|
|
24
24
|
name: "meta_graph_read",
|
|
25
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.
|
|
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
33
|
- Prefer dedicated tools (meta_business_list_assets, meta_ads_get_insights, …) when they exist. Use this as a last resort.`,
|
|
34
34
|
inputSchema: inputSchema.shape,
|
|
35
35
|
annotations: {
|
|
@@ -21,16 +21,16 @@ export const inputSchema = z
|
|
|
21
21
|
export const definition = {
|
|
22
22
|
name: "meta_business_overview",
|
|
23
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
|
-
|
|
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
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
35
|
inputSchema: inputSchema.shape,
|
|
36
36
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|