@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.
Files changed (37) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +292 -292
  3. package/dist/constants.d.ts +1 -1
  4. package/dist/constants.js +1 -1
  5. package/dist/errors.js +34 -4
  6. package/dist/helpers/graph-client.d.ts +24 -0
  7. package/dist/helpers/graph-client.js +50 -8
  8. package/dist/index.js +0 -0
  9. package/dist/tools/ads/get-insights.d.ts +1 -1
  10. package/dist/tools/ads/get-insights.js +10 -10
  11. package/dist/tools/ads/list-accounts.js +4 -4
  12. package/dist/tools/business/list-assets.js +10 -10
  13. package/dist/tools/business/list-businesses.js +4 -4
  14. package/dist/tools/business/list-system-users.js +4 -4
  15. package/dist/tools/catalog/list-products.d.ts +1 -1
  16. package/dist/tools/instagram/get-audience-demographics.d.ts +1 -1
  17. package/dist/tools/instagram/get-audience-demographics.js +8 -3
  18. package/dist/tools/instagram/get-media-insights.d.ts +1 -1
  19. package/dist/tools/instagram/get-media-insights.js +20 -14
  20. package/dist/tools/instagram/list-accounts.js +2 -2
  21. package/dist/tools/meta/graph-read.js +7 -7
  22. package/dist/tools/overview/business-overview.js +31 -22
  23. package/dist/tools/pages/get-insights.js +2 -2
  24. package/dist/tools/pages/get-post-insights.d.ts +5 -1
  25. package/dist/tools/pages/get-post-insights.js +16 -5
  26. package/dist/tools/pages/list-posts.d.ts +1 -1
  27. package/dist/tools/pages/list-posts.js +12 -6
  28. package/dist/tools/pages/list-reviews.js +2 -2
  29. package/dist/tools/pages/list-videos.js +2 -2
  30. package/dist/tools/pixels/list.js +6 -3
  31. package/dist/tools/shared.d.ts +10 -0
  32. package/dist/tools/shared.js +26 -0
  33. package/dist/tools/token/health.js +6 -6
  34. package/dist/tools/token/inspect.js +11 -11
  35. package/dist/tools/whatsapp/get-analytics.js +2 -2
  36. package/dist/tools/whatsapp/list-templates.d.ts +1 -1
  37. package/package.json +77 -76
@@ -1,9 +1,12 @@
1
1
  import { z } from "zod";
2
2
  import { metaIdSchema } from "../../helpers/schema.js";
3
- import { runGet } from "../shared.js";
3
+ import { errorResult, runGetAsPage } from "../shared.js";
4
4
  export const inputSchema = z
5
5
  .object({
6
- post_id: metaIdSchema.describe("Post ID (format '{page_id}_{post_id}' or shortform)."),
6
+ post_id: metaIdSchema.describe("Post ID. Meta uses the '{page_id}_{post_id}' format — pass it in full so the server can resolve the parent Page's access token automatically."),
7
+ page_id: metaIdSchema
8
+ .optional()
9
+ .describe("Optional explicit Page ID. Use when the post_id is not in the '{page_id}_{post_id}' form (rare)."),
7
10
  metrics: z
8
11
  .array(z.string())
9
12
  .default([
@@ -18,18 +21,26 @@ export const inputSchema = z
18
21
  "post_video_views_unique",
19
22
  "post_video_avg_time_watched",
20
23
  ])
21
- .describe("Post-level insight metrics. See https://developers.facebook.com/docs/graph-api/reference/v23.0/insights"),
24
+ .describe("Post-level insight metrics. See https://developers.facebook.com/docs/graph-api/reference/v23.0/insights. Requires 'read_insights' scope. Note: Meta will reject the call if any single metric is invalid for the post type — narrow the list if you see (#100)."),
22
25
  })
23
26
  .strict();
24
27
  export const definition = {
25
28
  name: "meta_page_get_post_insights",
26
29
  title: "Get insights for a single Page post",
27
- 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.`,
30
+ description: `Reads impressions, unique reach, paid vs. organic split, engaged users, click counts, reactions-by-type, and video view metrics for a single post.
31
+
32
+ Uses 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.`,
28
33
  inputSchema: inputSchema.shape,
29
34
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
30
35
  };
31
36
  export async function handler(input, ctx) {
32
- return runGet(ctx, {
37
+ // Resolve the parent Page id either from an explicit input or from the
38
+ // '{page_id}_{post_id}' prefix of the post_id.
39
+ const parentPageId = input.page_id ?? input.post_id.split("_")[0];
40
+ if (!/^\d+$/.test(parentPageId)) {
41
+ return errorResult(new Error(`Could not infer parent Page ID from post_id '${input.post_id}'. Pass page_id explicitly.`));
42
+ }
43
+ return runGetAsPage(ctx, parentPageId, {
33
44
  path: `${input.post_id}/insights`,
34
45
  params: { metric: input.metrics.join(",") },
35
46
  });
@@ -20,9 +20,9 @@ export declare const inputSchema: z.ZodObject<{
20
20
  after?: string | undefined;
21
21
  }, {
22
22
  page_id: string;
23
+ fields?: string[] | undefined;
23
24
  since?: string | undefined;
24
25
  until?: string | undefined;
25
- fields?: string[] | undefined;
26
26
  limit?: number | undefined;
27
27
  after?: string | undefined;
28
28
  auto_paginate?: boolean | undefined;
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { assertAllowed } from "../../config.js";
3
3
  import { metaIdSchema, paginationShape } from "../../helpers/schema.js";
4
- import { runList } from "../shared.js";
4
+ import { runListAsPage } from "../shared.js";
5
5
  export const inputSchema = z
6
6
  .object({
7
7
  page_id: metaIdSchema,
@@ -14,21 +14,25 @@ export const inputSchema = z
14
14
  fields: z
15
15
  .array(z.string())
16
16
  .default([
17
+ // Note: 'subattachments' is deprecated as of Graph API v3.3+ for
18
+ // aggregate field expansion (#12 deprecate_post_aggregated_fields_for_attachement).
19
+ // Removed from default. Likewise 'is_published' and 'type' are
20
+ // deprecated for some post kinds — kept out of the default to keep
21
+ // the call shape valid across all post types. Re-add via the fields
22
+ // input if you need them and have verified the call still passes.
17
23
  "id",
18
24
  "message",
19
25
  "created_time",
20
26
  "updated_time",
21
27
  "permalink_url",
22
28
  "status_type",
23
- "type",
24
- "is_published",
25
29
  "from",
26
- "attachments{media_type,title,url,subattachments}",
30
+ "attachments{media_type,title,url}",
27
31
  "reactions.summary(true).limit(0)",
28
32
  "shares",
29
33
  "comments.summary(true).limit(0)",
30
34
  ])
31
- .describe("Post fields. Default includes reaction + comment counts via summary."),
35
+ .describe("Post fields. Default includes reaction + comment counts via summary. 'subattachments', 'is_published' and 'type' are excluded by default — Meta deprecated them on certain post types and including them rejects the whole call."),
32
36
  ...paginationShape,
33
37
  })
34
38
  .strict();
@@ -41,7 +45,9 @@ export const definition = {
41
45
  };
42
46
  export async function handler(input, ctx) {
43
47
  assertAllowed("page", input.page_id, ctx.config);
44
- return runList(ctx, {
48
+ // Page-level edges (/posts, /published_posts, /feed) require a Page access
49
+ // token, not the system-user token. runListAsPage resolves it first.
50
+ return runListAsPage(ctx, input.page_id, {
45
51
  path: `${input.page_id}/${input.edge}`,
46
52
  params: {
47
53
  fields: input.fields.join(","),
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { assertAllowed } from "../../config.js";
3
3
  import { metaIdSchema, paginationShape } from "../../helpers/schema.js";
4
- import { runList } from "../shared.js";
4
+ import { runListAsPage } from "../shared.js";
5
5
  export const inputSchema = z
6
6
  .object({
7
7
  page_id: metaIdSchema,
@@ -30,7 +30,7 @@ export const definition = {
30
30
  };
31
31
  export async function handler(input, ctx) {
32
32
  assertAllowed("page", input.page_id, ctx.config);
33
- return runList(ctx, {
33
+ return runListAsPage(ctx, input.page_id, {
34
34
  path: `${input.page_id}/ratings`,
35
35
  params: { fields: input.fields.join(","), limit: input.limit, after: input.after },
36
36
  }, { auto_paginate: input.auto_paginate, after: input.after, limit: input.limit }, { page_id: input.page_id });
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { assertAllowed } from "../../config.js";
3
3
  import { metaIdSchema, paginationShape } from "../../helpers/schema.js";
4
- import { runList } from "../shared.js";
4
+ import { runListAsPage } from "../shared.js";
5
5
  export const inputSchema = z
6
6
  .object({
7
7
  page_id: metaIdSchema,
@@ -33,7 +33,7 @@ export const definition = {
33
33
  };
34
34
  export async function handler(input, ctx) {
35
35
  assertAllowed("page", input.page_id, ctx.config);
36
- return runList(ctx, {
36
+ return runListAsPage(ctx, input.page_id, {
37
37
  path: `${input.page_id}/videos`,
38
38
  params: { fields: input.fields.join(","), limit: input.limit, after: input.after },
39
39
  }, { auto_paginate: input.auto_paginate, after: input.after, limit: input.limit }, { page_id: input.page_id });
@@ -8,17 +8,20 @@ export const inputSchema = z
8
8
  fields: z
9
9
  .array(z.string())
10
10
  .default([
11
+ // 'code' (the pixel's JS snippet) and 'owner_ad_account' both require
12
+ // ads_management on the *owning* ad account, not just the business.
13
+ // Reading them with a system-user token that wasn't granted that
14
+ // task fails the whole list with (#200). We omit them from the
15
+ // defaults so the list stays useful; callers can re-add explicitly.
11
16
  "id",
12
17
  "name",
13
- "code",
14
18
  "is_created_by_business",
15
19
  "is_unavailable",
16
20
  "last_fired_time",
17
21
  "creation_time",
18
- "owner_ad_account",
19
22
  "owner_business",
20
23
  ])
21
- .describe("Pixel fields."),
24
+ .describe("Pixel fields. 'code' and 'owner_ad_account' are NOT in the default — they require ads_management on the owning ad account. Re-add them only when the system user has that task on every pixel's owner account."),
22
25
  ...paginationShape,
23
26
  })
24
27
  .strict();
@@ -18,3 +18,13 @@ export declare function runList<T>(ctx: ToolContext, opts: GraphGetOptions, pag:
18
18
  /** Run a single read and return a tool result. */
19
19
  export declare function runGet<T>(ctx: ToolContext, opts: GraphGetOptions, extra?: Record<string, unknown>): Promise<ToolTextResult>;
20
20
  export declare function errorResult(err: unknown): ToolTextResult;
21
+ /**
22
+ * Resolves a Page access token for `pageId`, then runs `runList` with it as
23
+ * the access-token override. Page-level edges (`/{page_id}/posts`,
24
+ * `/{page_id}/insights`, `/{page_id}/ratings`, `/{page_id}/videos`,
25
+ * `/{post_id}/insights`) require a Page access token rather than the
26
+ * configured system-user token.
27
+ */
28
+ export declare function runListAsPage<T>(ctx: ToolContext, pageId: string, opts: GraphGetOptions, pag: ListRunOpts, extra?: Record<string, unknown>): Promise<ToolTextResult>;
29
+ /** Same as runGet but resolves and uses the Page access token first. */
30
+ export declare function runGetAsPage<T>(ctx: ToolContext, pageId: string, opts: GraphGetOptions, extra?: Record<string, unknown>): Promise<ToolTextResult>;
@@ -53,3 +53,29 @@ export function errorResult(err) {
53
53
  retryable: e.retryable,
54
54
  });
55
55
  }
56
+ /**
57
+ * Resolves a Page access token for `pageId`, then runs `runList` with it as
58
+ * the access-token override. Page-level edges (`/{page_id}/posts`,
59
+ * `/{page_id}/insights`, `/{page_id}/ratings`, `/{page_id}/videos`,
60
+ * `/{post_id}/insights`) require a Page access token rather than the
61
+ * configured system-user token.
62
+ */
63
+ export async function runListAsPage(ctx, pageId, opts, pag, extra = {}) {
64
+ try {
65
+ const pageToken = await ctx.graph.getPageAccessToken(pageId);
66
+ return runList(ctx, { ...opts, accessTokenOverride: pageToken }, pag, extra);
67
+ }
68
+ catch (err) {
69
+ return errorResult(err);
70
+ }
71
+ }
72
+ /** Same as runGet but resolves and uses the Page access token first. */
73
+ export async function runGetAsPage(ctx, pageId, opts, extra = {}) {
74
+ try {
75
+ const pageToken = await ctx.graph.getPageAccessToken(pageId);
76
+ return runGet(ctx, { ...opts, accessTokenOverride: pageToken }, extra);
77
+ }
78
+ catch (err) {
79
+ return errorResult(err);
80
+ }
81
+ }
@@ -5,12 +5,12 @@ export const inputSchema = z.object({}).strict();
5
5
  export const definition = {
6
6
  name: "meta_health_check",
7
7
  title: "Meta MCP health check",
8
- description: `End-to-end reachability probe:
9
- - Confirms the Graph API is reachable.
10
- - Confirms the configured token resolves to a valid identity (via /me).
11
- - Surfaces the latest rate-limit header snapshot from the Graph client.
12
- - Lists which allowlists are active (business / ad account / page / IG user).
13
-
8
+ description: `End-to-end reachability probe:
9
+ - Confirms the Graph API is reachable.
10
+ - Confirms the configured token resolves to a valid identity (via /me).
11
+ - Surfaces the latest rate-limit header snapshot from the Graph client.
12
+ - Lists which allowlists are active (business / ad account / page / IG user).
13
+
14
14
  Use when a session starts, or when diagnosing why other tools are returning errors.`,
15
15
  inputSchema: inputSchema.shape,
16
16
  annotations: {
@@ -10,17 +10,17 @@ export const inputSchema = z
10
10
  export const definition = {
11
11
  name: "meta_token_inspect",
12
12
  title: "Inspect Meta access token",
13
- description: `Decodes the configured META_ACCESS_TOKEN via Graph API /debug_token.
14
-
15
- Returns:
16
- - app_id + application name
17
- - token type (system-user / user / page)
18
- - is_valid
19
- - expires_at + data_access_expires_at (unix seconds; 0 = never expires)
20
- - scopes + granular_scopes (per-asset targeting)
21
-
22
- Use this first when troubleshooting — almost every other tool failure traces back to a missing scope or an asset not assigned to the token.
23
-
13
+ description: `Decodes the configured META_ACCESS_TOKEN via Graph API /debug_token.
14
+
15
+ Returns:
16
+ - app_id + application name
17
+ - token type (system-user / user / page)
18
+ - is_valid
19
+ - expires_at + data_access_expires_at (unix seconds; 0 = never expires)
20
+ - scopes + granular_scopes (per-asset targeting)
21
+
22
+ Use this first when troubleshooting — almost every other tool failure traces back to a missing scope or an asset not assigned to the token.
23
+
24
24
  Never logs the token itself.`,
25
25
  inputSchema: inputSchema.shape,
26
26
  annotations: {
@@ -30,8 +30,8 @@ export const inputSchema = z
30
30
  export const definition = {
31
31
  name: "meta_whatsapp_get_analytics",
32
32
  title: "Get WhatsApp Business analytics",
33
- description: `Fetches WABA analytics — either the legacy 'analytics' field (message counts) or the richer 'conversation_analytics' (per-conversation pricing, categories: AUTHENTICATION/MARKETING/SERVICE/UTILITY).
34
-
33
+ description: `Fetches WABA analytics — either the legacy 'analytics' field (message counts) or the richer 'conversation_analytics' (per-conversation pricing, categories: AUTHENTICATION/MARKETING/SERVICE/UTILITY).
34
+
35
35
  Pass start + end as Unix seconds. Use granularity DAY for most reports. Filter by phone_numbers, country_codes, or conversation_categories to narrow.`,
36
36
  inputSchema: inputSchema.shape,
37
37
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
@@ -16,8 +16,8 @@ export declare const inputSchema: z.ZodObject<{
16
16
  after?: string | undefined;
17
17
  }, {
18
18
  waba_id: string;
19
- status?: "PAUSED" | "DELETED" | "APPROVED" | "PENDING" | "REJECTED" | "DISABLED" | "IN_APPEAL" | undefined;
20
19
  fields?: string[] | undefined;
20
+ status?: "PAUSED" | "DELETED" | "APPROVED" | "PENDING" | "REJECTED" | "DISABLED" | "IN_APPEAL" | undefined;
21
21
  limit?: number | undefined;
22
22
  after?: string | undefined;
23
23
  auto_paginate?: boolean | undefined;
package/package.json CHANGED
@@ -1,76 +1,77 @@
1
- {
2
- "name": "@praise25/meta-mcp-server",
3
- "description": "Read-only Model Context Protocol server for Meta Business Manager — Pages, Instagram, Ads insights, Pixels, Catalog, WhatsApp.",
4
- "version": "0.1.1",
5
- "author": "Stephen A.",
6
- "license": "MIT",
7
- "homepage": "https://github.com/feladeveloper/meta-mcp-server#readme",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/feladeveloper/meta-mcp-server.git"
11
- },
12
- "bugs": {
13
- "url": "https://github.com/feladeveloper/meta-mcp-server/issues"
14
- },
15
- "keywords": [
16
- "mcp",
17
- "model-context-protocol",
18
- "meta",
19
- "facebook",
20
- "instagram",
21
- "marketing-api",
22
- "ads",
23
- "ads-insights",
24
- "pixels",
25
- "catalog",
26
- "whatsapp",
27
- "business-manager",
28
- "read-only",
29
- "ai-tools",
30
- "claude"
31
- ],
32
- "type": "module",
33
- "main": "dist/index.js",
34
- "bin": {
35
- "meta-business-manager-mcp-server": "dist/index.js"
36
- },
37
- "files": [
38
- "dist",
39
- "README.md",
40
- "LICENSE"
41
- ],
42
- "engines": {
43
- "node": ">=20.10.0"
44
- },
45
- "scripts": {
46
- "build:clean": "rm -rf dist",
47
- "build:compile": "tsc --project tsconfig.build.json",
48
- "build:chmod": "chmod +x dist/index.js || true",
49
- "build": "npm run build:clean && npm run build:compile && npm run build:chmod",
50
- "start": "node dist/index.js",
51
- "dev": "tsx src/index.ts",
52
- "inspect": "npm run build && npx @modelcontextprotocol/inspector dist/index.js",
53
- "check:types": "tsc --noEmit --project tsconfig.json",
54
- "test:readonly": "npm run build && node tests/read-only-guard.mjs",
55
- "test:placeholder": "npm run build && node tests/placeholder-rejection.mjs",
56
- "test:invariants": "npm run test:readonly && npm run test:placeholder",
57
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
58
- "prepublishOnly": "npm run check:types && npm run test:invariants"
59
- },
60
- "dependencies": {
61
- "@modelcontextprotocol/sdk": "^1.11.2",
62
- "axios": "^1.7.7",
63
- "lru-cache": "^11.1.0",
64
- "pino": "^9.5.0",
65
- "zod": "^3.23.8"
66
- },
67
- "devDependencies": {
68
- "@jest/globals": "^30.0.0",
69
- "@types/jest": "^30.0.0",
70
- "@types/node": "^22.0.0",
71
- "jest": "^30.0.0",
72
- "ts-jest": "^29.2.0",
73
- "tsx": "^4.19.0",
74
- "typescript": "^5.6.0"
75
- }
76
- }
1
+ {
2
+ "name": "@praise25/meta-mcp-server",
3
+ "description": "Read-only Model Context Protocol server for Meta Business Manager — Pages, Instagram, Ads insights, Pixels, Catalog, WhatsApp.",
4
+ "version": "0.1.5",
5
+ "author": "Stephen A.",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/feladeveloper/meta-mcp-server#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/feladeveloper/meta-mcp-server.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/feladeveloper/meta-mcp-server/issues"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "model-context-protocol",
18
+ "meta",
19
+ "facebook",
20
+ "instagram",
21
+ "marketing-api",
22
+ "ads",
23
+ "ads-insights",
24
+ "pixels",
25
+ "catalog",
26
+ "whatsapp",
27
+ "business-manager",
28
+ "read-only",
29
+ "ai-tools",
30
+ "claude"
31
+ ],
32
+ "type": "module",
33
+ "main": "dist/index.js",
34
+ "bin": {
35
+ "meta-business-manager-mcp-server": "dist/index.js"
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "README.md",
40
+ "LICENSE"
41
+ ],
42
+ "engines": {
43
+ "node": ">=20.10.0"
44
+ },
45
+ "scripts": {
46
+ "build:clean": "rm -rf dist",
47
+ "build:compile": "tsc --project tsconfig.build.json",
48
+ "build:chmod": "chmod +x dist/index.js || true",
49
+ "build": "npm run build:clean && npm run build:compile && npm run build:chmod",
50
+ "start": "node dist/index.js",
51
+ "dev": "tsx src/index.ts",
52
+ "inspect": "npm run build && npx @modelcontextprotocol/inspector dist/index.js",
53
+ "check:types": "tsc --noEmit --project tsconfig.json",
54
+ "test:readonly": "npm run build && node tests/read-only-guard.mjs",
55
+ "test:placeholder": "npm run build && node tests/placeholder-rejection.mjs",
56
+ "test:invariants": "npm run test:readonly && npm run test:placeholder",
57
+ "test:scenarios": "npm run build && node tests/scenarios.mjs",
58
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
59
+ "prepublishOnly": "npm run check:types && npm run test:invariants"
60
+ },
61
+ "dependencies": {
62
+ "@modelcontextprotocol/sdk": "^1.11.2",
63
+ "axios": "^1.7.7",
64
+ "lru-cache": "^11.1.0",
65
+ "pino": "^9.5.0",
66
+ "zod": "^3.23.8"
67
+ },
68
+ "devDependencies": {
69
+ "@jest/globals": "^30.0.0",
70
+ "@types/jest": "^30.0.0",
71
+ "@types/node": "^22.0.0",
72
+ "jest": "^30.0.0",
73
+ "ts-jest": "^29.2.0",
74
+ "tsx": "^4.19.0",
75
+ "typescript": "^5.6.0"
76
+ }
77
+ }