@praise25/meta-mcp-server 0.1.1 → 0.1.5
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/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/errors.js +34 -4
- package/dist/helpers/graph-client.d.ts +24 -0
- package/dist/helpers/graph-client.js +50 -8
- package/dist/index.js +0 -0
- package/dist/tools/ads/get-insights.d.ts +1 -1
- package/dist/tools/ads/get-insights.js +10 -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/catalog/list-products.d.ts +1 -1
- package/dist/tools/instagram/get-audience-demographics.d.ts +1 -1
- package/dist/tools/instagram/get-audience-demographics.js +8 -3
- package/dist/tools/instagram/get-media-insights.d.ts +1 -1
- package/dist/tools/instagram/get-media-insights.js +20 -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 +31 -22
- package/dist/tools/pages/get-insights.js +2 -2
- package/dist/tools/pages/get-post-insights.d.ts +5 -1
- package/dist/tools/pages/get-post-insights.js +16 -5
- package/dist/tools/pages/list-posts.d.ts +1 -1
- package/dist/tools/pages/list-posts.js +12 -6
- package/dist/tools/pages/list-reviews.js +2 -2
- package/dist/tools/pages/list-videos.js +2 -2
- package/dist/tools/pixels/list.js +6 -3
- package/dist/tools/shared.d.ts +10 -0
- package/dist/tools/shared.js +26 -0
- package/dist/tools/token/health.js +6 -6
- package/dist/tools/token/inspect.js +11 -11
- package/dist/tools/whatsapp/get-analytics.js +2 -2
- package/dist/tools/whatsapp/list-templates.d.ts +1 -1
- package/package.json +77 -76
package/dist/errors.js
CHANGED
|
@@ -16,7 +16,7 @@ export class MetaError extends Error {
|
|
|
16
16
|
this.type = opts.type;
|
|
17
17
|
this.httpStatus = opts.httpStatus;
|
|
18
18
|
this.retryable = opts.code != null && RETRYABLE_META_CODES.has(opts.code);
|
|
19
|
-
this.hint = opts.hint ?? deriveHint(opts.code, opts.subcode, opts.httpStatus);
|
|
19
|
+
this.hint = opts.hint ?? deriveHint(opts.code, opts.subcode, opts.httpStatus, message);
|
|
20
20
|
}
|
|
21
21
|
toJSON() {
|
|
22
22
|
return {
|
|
@@ -32,7 +32,7 @@ export class MetaError extends Error {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
function deriveHint(code, subcode, httpStatus) {
|
|
35
|
+
function deriveHint(code, subcode, httpStatus, message) {
|
|
36
36
|
if (code === 190) {
|
|
37
37
|
if (subcode === 463)
|
|
38
38
|
return "Access token has expired. Generate a new system-user token.";
|
|
@@ -40,8 +40,29 @@ function deriveHint(code, subcode, httpStatus) {
|
|
|
40
40
|
return "Access token is invalid. Re-issue from Business Settings → System Users.";
|
|
41
41
|
return "Access token error. Check META_ACCESS_TOKEN is valid and tied to the correct app.";
|
|
42
42
|
}
|
|
43
|
-
if (code === 10
|
|
44
|
-
|
|
43
|
+
if (code === 10) {
|
|
44
|
+
// Code 10 splits into two distinct operator actions, distinguishable by the
|
|
45
|
+
// exact Meta error message — surface the right one so AI consumers can
|
|
46
|
+
// direct the user accurately.
|
|
47
|
+
if (message && /Application does not have permission/i.test(message)) {
|
|
48
|
+
return "App-level permission missing. The configured app needs Standard Access via Meta App Review for the relevant permission (e.g. instagram_manage_insights, ads_management). Request via App Review → Permissions and Features in the app dashboard. While in development tier, only developer/admin users on the app can read this data.";
|
|
49
|
+
}
|
|
50
|
+
if (message && /Page Public Content Access/i.test(message)) {
|
|
51
|
+
return "App needs the 'Page Public Content Access' feature, granted via Meta App Review. Until approved, pages_read_engagement on Pages outside the app's developer/test set will fail. Path: App Dashboard → App Review → Features.";
|
|
52
|
+
}
|
|
53
|
+
return "App-level permission denied (code 10). Either the configured app lacks the required permission/feature in App Review, or the system user is not assigned to the target asset with the right task in Business Settings.";
|
|
54
|
+
}
|
|
55
|
+
if (code === 200) {
|
|
56
|
+
if (message && /pages_read_user_content/i.test(message)) {
|
|
57
|
+
return "Token lacks 'pages_read_user_content'. Regenerate the system-user token in Business Settings → System Users → Generate New Token, ticking that scope.";
|
|
58
|
+
}
|
|
59
|
+
if (message && /This application has not been approved/i.test(message)) {
|
|
60
|
+
return "App not approved for this product. Add the relevant Product (e.g. WhatsApp Business Platform, Commerce / Catalog Management) to the app in https://developers.facebook.com/apps and complete its onboarding.";
|
|
61
|
+
}
|
|
62
|
+
return "Permission denied. Verify the system user is assigned to the target asset (Business Settings → System Users → Add Assets) with the right task, and the token has the matching scope.";
|
|
63
|
+
}
|
|
64
|
+
if (code === 283) {
|
|
65
|
+
return "Token lacks the scope needed for this Page action (typically pages_read_user_content for reviews, or pages_read_engagement for posts). Regenerate the token with the required scope ticked.";
|
|
45
66
|
}
|
|
46
67
|
if (code === 4 || code === 17 || code === 32) {
|
|
47
68
|
return "Rate-limited by Meta. Retry with backoff or narrow the query (shorter date range, fewer breakdowns).";
|
|
@@ -49,7 +70,16 @@ function deriveHint(code, subcode, httpStatus) {
|
|
|
49
70
|
if (code === 613) {
|
|
50
71
|
return "Ad account throttled. Reduce insight query complexity or wait before retrying.";
|
|
51
72
|
}
|
|
73
|
+
if (code === 210) {
|
|
74
|
+
return "This endpoint requires a Page access token, not the system-user token. The Page tools resolve this automatically — if you see this from meta_graph_read, fetch the token via GET /{page_id}?fields=access_token first.";
|
|
75
|
+
}
|
|
76
|
+
if (code === 12) {
|
|
77
|
+
return "Field deprecated by Meta. Remove the deprecated field from the `fields` array. Common offender: 'subattachments' inside attachments expansion (deprecated in v3.3+).";
|
|
78
|
+
}
|
|
52
79
|
if (code === 100) {
|
|
80
|
+
if (message && /must be a valid insights metric/i.test(message)) {
|
|
81
|
+
return "Either one of the supplied metrics is invalid for this Page/period combination, OR the token lacks 'read_insights' (Meta returns this misleading error in both cases). Check the token has read_insights via meta_token_inspect, then narrow your metrics list to a known-valid subset.";
|
|
82
|
+
}
|
|
53
83
|
return "Invalid parameter. Check field names, ID format (ad accounts need 'act_' prefix), and date presets.";
|
|
54
84
|
}
|
|
55
85
|
if (httpStatus === 404) {
|
|
@@ -9,6 +9,16 @@ export interface GraphGetOptions {
|
|
|
9
9
|
noCache?: boolean;
|
|
10
10
|
/** Override the API version for this call. */
|
|
11
11
|
apiVersion?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Use a Page-specific access token instead of the configured system-user
|
|
14
|
+
* token for this request. Required for Page-level edges like /{page_id}/posts,
|
|
15
|
+
* /{page_id}/insights, /{page_id}/ratings, /{page_id}/videos and for post-
|
|
16
|
+
* insights endpoints. Use `getPageAccessToken(pageId)` to fetch one.
|
|
17
|
+
*
|
|
18
|
+
* `appsecret_proof` is recomputed against this token so the cryptographic
|
|
19
|
+
* binding to the app remains intact.
|
|
20
|
+
*/
|
|
21
|
+
accessTokenOverride?: string;
|
|
12
22
|
}
|
|
13
23
|
export interface GraphPagedResponse<T = unknown> {
|
|
14
24
|
data: T[];
|
|
@@ -33,10 +43,24 @@ export declare class GraphClient {
|
|
|
33
43
|
private readonly logger;
|
|
34
44
|
private readonly http;
|
|
35
45
|
private readonly cache;
|
|
46
|
+
private readonly pageTokens;
|
|
36
47
|
private lastRateLimit;
|
|
37
48
|
constructor(config: Config, logger: Logger);
|
|
38
49
|
get rateLimit(): GraphRateLimitHeaders;
|
|
39
50
|
clearCache(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the Page access token for a Page id. Cached process-locally for
|
|
53
|
+
* the lifetime of the GraphClient (Page tokens don't expire as fast as
|
|
54
|
+
* user tokens, and this avoids paying the round-trip cost on every Page
|
|
55
|
+
* call). Page-level Graph edges (`/posts`, `/ratings`, `/videos`,
|
|
56
|
+
* `/insights`, `/{post_id}/insights`) require a Page access token rather
|
|
57
|
+
* than the configured system-user token; without it Meta returns
|
|
58
|
+
* `(#210) A page access token is required`.
|
|
59
|
+
*
|
|
60
|
+
* Requires the configured system user to be assigned to the Page with
|
|
61
|
+
* at least the "View performance" or "Analyze Page" task.
|
|
62
|
+
*/
|
|
63
|
+
getPageAccessToken(pageId: string): Promise<string>;
|
|
40
64
|
get<T = GraphPagedResponse>(opts: GraphGetOptions): Promise<T>;
|
|
41
65
|
/**
|
|
42
66
|
* Follow `paging.next` cursors and concatenate `data` arrays up to maxPages.
|
|
@@ -8,6 +8,7 @@ export class GraphClient {
|
|
|
8
8
|
logger;
|
|
9
9
|
http;
|
|
10
10
|
cache;
|
|
11
|
+
pageTokens = new Map();
|
|
11
12
|
lastRateLimit = {};
|
|
12
13
|
constructor(config, logger) {
|
|
13
14
|
this.config = config;
|
|
@@ -36,12 +37,46 @@ export class GraphClient {
|
|
|
36
37
|
}
|
|
37
38
|
clearCache() {
|
|
38
39
|
this.cache.clear();
|
|
40
|
+
this.pageTokens.clear();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the Page access token for a Page id. Cached process-locally for
|
|
44
|
+
* the lifetime of the GraphClient (Page tokens don't expire as fast as
|
|
45
|
+
* user tokens, and this avoids paying the round-trip cost on every Page
|
|
46
|
+
* call). Page-level Graph edges (`/posts`, `/ratings`, `/videos`,
|
|
47
|
+
* `/insights`, `/{post_id}/insights`) require a Page access token rather
|
|
48
|
+
* than the configured system-user token; without it Meta returns
|
|
49
|
+
* `(#210) A page access token is required`.
|
|
50
|
+
*
|
|
51
|
+
* Requires the configured system user to be assigned to the Page with
|
|
52
|
+
* at least the "View performance" or "Analyze Page" task.
|
|
53
|
+
*/
|
|
54
|
+
async getPageAccessToken(pageId) {
|
|
55
|
+
if (!pageId)
|
|
56
|
+
throw new MetaError("getPageAccessToken: pageId is required");
|
|
57
|
+
const cached = this.pageTokens.get(pageId);
|
|
58
|
+
if (cached)
|
|
59
|
+
return cached;
|
|
60
|
+
const resp = await this.get({
|
|
61
|
+
path: pageId,
|
|
62
|
+
params: { fields: "access_token,id,name" },
|
|
63
|
+
// Don't cache via the URL+params LRU because the value is sensitive and
|
|
64
|
+
// we want to keep the per-page Map as the single source of truth.
|
|
65
|
+
noCache: true,
|
|
66
|
+
});
|
|
67
|
+
if (!resp.access_token) {
|
|
68
|
+
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
|
+
hint: "In Business Settings → System Users → AI_Insights_Reader → Add Assets, assign this Page with at least 'View performance' or 'Analyze Page' tasks.",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
this.pageTokens.set(pageId, resp.access_token);
|
|
73
|
+
return resp.access_token;
|
|
39
74
|
}
|
|
40
75
|
async get(opts) {
|
|
41
76
|
const version = opts.apiVersion ?? this.config.apiVersion;
|
|
42
77
|
const path = opts.path.replace(/^\/+/, "");
|
|
43
78
|
const url = `/${version}/${path}`;
|
|
44
|
-
const params = this.buildParams(opts.params);
|
|
79
|
+
const params = this.buildParams(opts.params, opts.accessTokenOverride);
|
|
45
80
|
const cacheKey = this.cacheKeyFor(url, params);
|
|
46
81
|
if (!opts.noCache) {
|
|
47
82
|
const cached = this.cache.get(cacheKey);
|
|
@@ -71,7 +106,12 @@ export class GraphClient {
|
|
|
71
106
|
collected.push(...page.data);
|
|
72
107
|
nextAfter = page.paging?.cursors?.after;
|
|
73
108
|
if (page.paging?.next && pages < cap) {
|
|
74
|
-
|
|
109
|
+
// Propagate the original accessTokenOverride to subsequent pages —
|
|
110
|
+
// optsFromNextUrl strips access_token from the URL but the override
|
|
111
|
+
// (if any) must continue to be applied.
|
|
112
|
+
const next = this.optsFromNextUrl(page.paging.next);
|
|
113
|
+
next.accessTokenOverride = opts.accessTokenOverride;
|
|
114
|
+
currentOpts = next;
|
|
75
115
|
}
|
|
76
116
|
else {
|
|
77
117
|
currentOpts = null;
|
|
@@ -92,24 +132,26 @@ export class GraphClient {
|
|
|
92
132
|
}
|
|
93
133
|
return { path, params, apiVersion: version };
|
|
94
134
|
}
|
|
95
|
-
buildParams(params) {
|
|
135
|
+
buildParams(params, accessTokenOverride) {
|
|
96
136
|
const out = {};
|
|
97
137
|
for (const [k, v] of Object.entries(params ?? {})) {
|
|
98
138
|
if (v === undefined || v === null)
|
|
99
139
|
continue;
|
|
100
140
|
out[k] = v;
|
|
101
141
|
}
|
|
102
|
-
|
|
142
|
+
const token = accessTokenOverride ?? this.config.accessToken;
|
|
143
|
+
out.access_token = token;
|
|
103
144
|
if (this.config.appSecret) {
|
|
104
|
-
out.appsecret_proof = this.appsecretProof();
|
|
145
|
+
out.appsecret_proof = this.appsecretProof(token);
|
|
105
146
|
}
|
|
106
147
|
return out;
|
|
107
148
|
}
|
|
108
|
-
appsecretProof() {
|
|
109
|
-
// appsecret_proof = HMAC-SHA256(access_token, app_secret)
|
|
149
|
+
appsecretProof(token) {
|
|
150
|
+
// appsecret_proof = HMAC-SHA256(access_token, app_secret).
|
|
151
|
+
// Recompute against whichever token is in use (system-user OR Page).
|
|
110
152
|
return crypto
|
|
111
153
|
.createHmac("sha256", this.config.appSecret)
|
|
112
|
-
.update(this.config.accessToken)
|
|
154
|
+
.update(token ?? this.config.accessToken)
|
|
113
155
|
.digest("hex");
|
|
114
156
|
}
|
|
115
157
|
cacheKeyFor(url, params) {
|
package/dist/index.js
CHANGED
|
File without changes
|
|
@@ -58,8 +58,8 @@ export declare const inputSchema: z.ZodObject<{
|
|
|
58
58
|
}[] | undefined;
|
|
59
59
|
}, {
|
|
60
60
|
object_id: string;
|
|
61
|
-
sort?: string[] | undefined;
|
|
62
61
|
fields?: string[] | undefined;
|
|
62
|
+
sort?: string[] | undefined;
|
|
63
63
|
limit?: number | undefined;
|
|
64
64
|
after?: string | undefined;
|
|
65
65
|
auto_paginate?: boolean | undefined;
|
|
@@ -101,16 +101,16 @@ 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
|
-
Returns spend, impressions, clicks, CPC/CPM/CTR, reach, frequency, actions (conversions), and video metrics — with optional demographic / placement / device breakdowns.
|
|
107
|
-
|
|
108
|
-
Tips for the AI consuming this:
|
|
109
|
-
- Default date_preset is last_30d. Override via date_preset or time_range.
|
|
110
|
-
- Set level to 'ad' and breakdowns=['publisher_platform','platform_position'] to see which placements convert.
|
|
111
|
-
- Use action_breakdowns=['action_type'] to split purchases vs. add_to_cart vs. lead.
|
|
112
|
-
- If you get code 613 or 17, you hit rate limits — narrow the date range or drop breakdowns.
|
|
113
|
-
|
|
104
|
+
description: `Fetch Marketing API insights for any ad object: ad account ('act_<id>'), campaign, ad set, or ad.
|
|
105
|
+
|
|
106
|
+
Returns spend, impressions, clicks, CPC/CPM/CTR, reach, frequency, actions (conversions), and video metrics — with optional demographic / placement / device breakdowns.
|
|
107
|
+
|
|
108
|
+
Tips for the AI consuming this:
|
|
109
|
+
- Default date_preset is last_30d. Override via date_preset or time_range.
|
|
110
|
+
- Set level to 'ad' and breakdowns=['publisher_platform','platform_position'] to see which placements convert.
|
|
111
|
+
- Use action_breakdowns=['action_type'] to split purchases vs. add_to_cart vs. lead.
|
|
112
|
+
- If you get code 613 or 17, you hit rate limits — narrow the date range or drop breakdowns.
|
|
113
|
+
|
|
114
114
|
Requires 'ads_read' token scope.`,
|
|
115
115
|
inputSchema: inputSchema.shape,
|
|
116
116
|
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: {
|
|
@@ -16,8 +16,8 @@ export declare const inputSchema: z.ZodObject<{
|
|
|
16
16
|
after?: string | undefined;
|
|
17
17
|
}, {
|
|
18
18
|
catalog_id: string;
|
|
19
|
-
filter?: string | undefined;
|
|
20
19
|
fields?: string[] | undefined;
|
|
20
|
+
filter?: string | undefined;
|
|
21
21
|
limit?: number | undefined;
|
|
22
22
|
after?: string | undefined;
|
|
23
23
|
auto_paginate?: boolean | undefined;
|
|
@@ -23,7 +23,7 @@ export type Input = z.infer<typeof inputSchema>;
|
|
|
23
23
|
export declare const definition: {
|
|
24
24
|
readonly name: "meta_ig_get_audience_demographics";
|
|
25
25
|
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\
|
|
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\n**Requirements (in order of frequency-of-failure):**\n1. Token has 'instagram_manage_insights' scope.\n2. The Hodusoft 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` on this endpoint specifically — even if the scope is on the token.\n3. The IG Business account must have ≥ 100 followers (Meta hides smaller accounts' demographics for privacy).\n\nIf you see code (#10), it's almost always #2. Track the App Review request and surface the gap to the operator; this tool is functioning correctly when it returns that error.";
|
|
27
27
|
readonly inputSchema: {
|
|
28
28
|
ig_user_id: z.ZodString;
|
|
29
29
|
metric: z.ZodDefault<z.ZodEnum<["follower_demographics", "engaged_audience_demographics", "reached_audience_demographics"]>>;
|
|
@@ -26,9 +26,14 @@ export const inputSchema = z
|
|
|
26
26
|
export const definition = {
|
|
27
27
|
name: "meta_ig_get_audience_demographics",
|
|
28
28
|
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
|
-
|
|
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
|
+
**Requirements (in order of frequency-of-failure):**
|
|
32
|
+
1. Token has 'instagram_manage_insights' scope.
|
|
33
|
+
2. The Hodusoft 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\` on this endpoint specifically — even if the scope is on the token.
|
|
34
|
+
3. The IG Business account must have ≥ 100 followers (Meta hides smaller accounts' demographics for privacy).
|
|
35
|
+
|
|
36
|
+
If you see code (#10), it's almost always #2. Track the App Review request and surface the gap to the operator; this tool is functioning correctly when it returns that error.`,
|
|
32
37
|
inputSchema: inputSchema.shape,
|
|
33
38
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
34
39
|
};
|
|
@@ -14,7 +14,7 @@ export type Input = z.infer<typeof inputSchema>;
|
|
|
14
14
|
export declare const definition: {
|
|
15
15
|
readonly name: "meta_ig_get_media_insights";
|
|
16
16
|
readonly title: "Get Instagram media insights";
|
|
17
|
-
readonly description: "Per-media metrics: reach,
|
|
17
|
+
readonly description: "Per-media metrics: reach, views, saves, likes, comments, shares, total_interactions, profile_visits, follows.\n\n**Requirements (in order of frequency-of-failure):**\n1. Token has 'instagram_manage_insights' scope.\n2. **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.\n3. The IG account must be Business or Creator (not personal).\n\n**Metric / media-type compatibility (post v22 deprecations):**\n- IMAGE / CAROUSEL_ALBUM: reach, saved, likes, comments, shares, total_interactions, profile_visits\n- VIDEO / REELS: views, reach, likes, comments, shares, saved, total_interactions ('impressions' is deprecated, use 'views')\n- STORY: views, reach, replies, navigation, profile_visits, follows (impressions is deprecated; use views)\n\nIf 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).";
|
|
18
18
|
readonly inputSchema: {
|
|
19
19
|
media_id: z.ZodString;
|
|
20
20
|
metrics: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
@@ -7,31 +7,37 @@ export const inputSchema = z
|
|
|
7
7
|
metrics: z
|
|
8
8
|
.array(z.string())
|
|
9
9
|
.default([
|
|
10
|
-
|
|
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.
|
|
11
14
|
"reach",
|
|
12
|
-
"
|
|
15
|
+
"views",
|
|
13
16
|
"likes",
|
|
14
17
|
"comments",
|
|
15
18
|
"shares",
|
|
19
|
+
"saved",
|
|
16
20
|
"total_interactions",
|
|
17
|
-
"profile_visits",
|
|
18
|
-
"follows",
|
|
19
|
-
"profile_activity",
|
|
20
21
|
])
|
|
21
|
-
.describe("IG media metrics.
|
|
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."),
|
|
22
23
|
})
|
|
23
24
|
.strict();
|
|
24
25
|
export const definition = {
|
|
25
26
|
name: "meta_ig_get_media_insights",
|
|
26
27
|
title: "Get Instagram media insights",
|
|
27
|
-
description: `Per-media metrics: reach,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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).`,
|
|
35
41
|
inputSchema: inputSchema.shape,
|
|
36
42
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
37
43
|
};
|
|
@@ -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: {
|
|
@@ -19,16 +19,16 @@ export const inputSchema = z
|
|
|
19
19
|
export const definition = {
|
|
20
20
|
name: "meta_business_overview",
|
|
21
21
|
title: "Eagle's-eye snapshot of a business",
|
|
22
|
-
description: `One-call consolidated read across the whole Meta surface for a business:
|
|
23
|
-
|
|
24
|
-
- Identity (system user + token app)
|
|
25
|
-
- Assigned Pages + each Page's high-level insights (impressions, engagement, fans) for the requested window
|
|
26
|
-
- Instagram Business accounts linked to those Pages (followers, media_count)
|
|
27
|
-
- Owned + client ad accounts with balance, spend cap, amount spent, and last-window insights (spend, impressions, clicks, CTR, CPC, reach)
|
|
28
|
-
- Pixels with last_fired_time (if include_pixels)
|
|
29
|
-
- Catalogs with product counts (if include_catalogs)
|
|
30
|
-
- WhatsApp Business Accounts + phone numbers (if include_whatsapp)
|
|
31
|
-
|
|
22
|
+
description: `One-call consolidated read across the whole Meta surface for a business:
|
|
23
|
+
|
|
24
|
+
- Identity (system user + token app)
|
|
25
|
+
- Assigned Pages + each Page's high-level insights (impressions, engagement, fans) for the requested window
|
|
26
|
+
- Instagram Business accounts linked to those Pages (followers, media_count)
|
|
27
|
+
- Owned + client ad accounts with balance, spend cap, amount spent, and last-window insights (spend, impressions, clicks, CTR, CPC, reach)
|
|
28
|
+
- Pixels with last_fired_time (if include_pixels)
|
|
29
|
+
- Catalogs with product counts (if include_catalogs)
|
|
30
|
+
- WhatsApp Business Accounts + phone numbers (if include_whatsapp)
|
|
31
|
+
|
|
32
32
|
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.`,
|
|
33
33
|
inputSchema: inputSchema.shape,
|
|
34
34
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
@@ -110,17 +110,22 @@ export async function handler(input, ctx) {
|
|
|
110
110
|
wabas,
|
|
111
111
|
]);
|
|
112
112
|
// Expand each page with linked IG + a small insights fetch.
|
|
113
|
+
// Page insights require the per-Page access token, so resolve it lazily.
|
|
113
114
|
const pagesExpanded = pagesR.ok && pagesR.data.data
|
|
114
115
|
? await Promise.all(pagesR.data.data.map(async (p) => {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
116
|
+
const igR = await ctx.graph
|
|
117
|
+
.get({
|
|
118
|
+
path: p.id,
|
|
119
|
+
params: { fields: "instagram_business_account{id,username,name,followers_count,media_count}" },
|
|
120
|
+
})
|
|
121
|
+
.then(ok)
|
|
122
|
+
.catch(fail);
|
|
123
|
+
// Resolve Page access token for the insights call. If we can't,
|
|
124
|
+
// record a per-page error rather than blanking the whole report.
|
|
125
|
+
let insightsR;
|
|
126
|
+
try {
|
|
127
|
+
const pageToken = await ctx.graph.getPageAccessToken(p.id);
|
|
128
|
+
insightsR = await ctx.graph
|
|
124
129
|
.get({
|
|
125
130
|
path: `${p.id}/insights`,
|
|
126
131
|
params: {
|
|
@@ -128,10 +133,14 @@ export async function handler(input, ctx) {
|
|
|
128
133
|
metric: "page_impressions,page_post_engagements,page_fans,page_views_total",
|
|
129
134
|
date_preset: input.date_preset,
|
|
130
135
|
},
|
|
136
|
+
accessTokenOverride: pageToken,
|
|
131
137
|
})
|
|
132
|
-
.then(ok)
|
|
133
|
-
.catch(fail)
|
|
134
|
-
|
|
138
|
+
.then((d) => ok({ data: d.data }))
|
|
139
|
+
.catch(fail);
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
insightsR = fail(err);
|
|
143
|
+
}
|
|
135
144
|
return {
|
|
136
145
|
id: p.id,
|
|
137
146
|
name: p.name,
|
|
@@ -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 { runGetAsPage } from "../shared.js";
|
|
5
5
|
const PERIOD = z.enum(["day", "week", "days_28", "lifetime", "total_over_range"]);
|
|
6
6
|
export const inputSchema = z
|
|
7
7
|
.object({
|
|
@@ -37,7 +37,7 @@ export const definition = {
|
|
|
37
37
|
};
|
|
38
38
|
export async function handler(input, ctx) {
|
|
39
39
|
assertAllowed("page", input.page_id, ctx.config);
|
|
40
|
-
return
|
|
40
|
+
return runGetAsPage(ctx, input.page_id, {
|
|
41
41
|
path: `${input.page_id}/insights`,
|
|
42
42
|
params: {
|
|
43
43
|
metric: input.metrics.join(","),
|
|
@@ -2,21 +2,25 @@ import { z } from "zod";
|
|
|
2
2
|
import type { ToolContext } from "../../context.js";
|
|
3
3
|
export declare const inputSchema: z.ZodObject<{
|
|
4
4
|
post_id: z.ZodString;
|
|
5
|
+
page_id: z.ZodOptional<z.ZodString>;
|
|
5
6
|
metrics: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
6
7
|
}, "strict", z.ZodTypeAny, {
|
|
7
8
|
post_id: string;
|
|
8
9
|
metrics: string[];
|
|
10
|
+
page_id?: string | undefined;
|
|
9
11
|
}, {
|
|
10
12
|
post_id: string;
|
|
13
|
+
page_id?: string | undefined;
|
|
11
14
|
metrics?: string[] | undefined;
|
|
12
15
|
}>;
|
|
13
16
|
export type Input = z.infer<typeof inputSchema>;
|
|
14
17
|
export declare const definition: {
|
|
15
18
|
readonly name: "meta_page_get_post_insights";
|
|
16
19
|
readonly title: "Get insights for a single Page post";
|
|
17
|
-
readonly description: "Reads impressions, unique reach, paid vs. organic split, engaged users, click counts, reactions-by-type, and video view metrics for a single post. Requires 'read_insights' scope.";
|
|
20
|
+
readonly description: "Reads impressions, unique reach, paid vs. organic split, engaged users, click counts, reactions-by-type, and video view metrics for a single post.\n\nUses the parent Page's access token (auto-resolved from the '{page_id}_{post_id}' prefix; pass page_id explicitly if your post_id isn't in that form). Requires the system user to be assigned to the Page with 'View performance' or 'Analyze Page' tasks, plus 'read_insights' scope on the token.";
|
|
18
21
|
readonly inputSchema: {
|
|
19
22
|
post_id: z.ZodString;
|
|
23
|
+
page_id: z.ZodOptional<z.ZodString>;
|
|
20
24
|
metrics: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
21
25
|
};
|
|
22
26
|
readonly annotations: {
|