@praise25/meta-mcp-server 0.1.2 → 0.1.6

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 CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 CashToken Rewards
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CashToken Rewards
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,292 +1,292 @@
1
- # meta-mcp-server
2
-
3
- > **Read-only Model Context Protocol server for Meta Business Manager.** Plug-in for AI assistants ("ChatGPT for marketing insights") that surfaces Pages, Instagram Business, Marketing API ad insights, Pixels, Commerce catalogs, and WhatsApp Business data — all read-only by construction.
4
-
5
- [![Read-Only Verified](https://img.shields.io/badge/safety-read--only-success)](#read-only-by-construction)
6
- [![Governance: Cashtoken SDGP](https://img.shields.io/badge/governance-cashtoken%20sdgp-blue)](./governance/standards/sdgp-main.md)
7
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
8
-
9
- ---
10
-
11
- ## Why this exists
12
-
13
- CashToken Marketing operates several Pages, Instagram Business accounts, Meta ad accounts, Pixels and (in future) Catalogs / WhatsApp Business assets across the `Hodusoft` Business Manager. Insight retrieval today is fragmented across Business Suite UIs, Ads Manager exports, and ad-hoc Graph API calls. This MCP server consolidates **read-only** access into a single tool surface that any MCP-aware AI assistant can plug into — turning fragmented UIs into one conversational interface for the marketing team.
14
-
15
- ---
16
-
17
- ## Read-only by construction
18
-
19
- This server cannot write to Meta. Period. Four layers of defence:
20
-
21
- 1. **HTTP interceptor** — every axios request passes through a guard that throws `ReadOnlyViolationError` if the method isn't `GET`, before the request leaves the process. ([`src/helpers/graph-client.ts`](./src/helpers/graph-client.ts))
22
- 2. **No write API surface** — the `GraphClient` class exposes only `get()` and `getAllPages()`. No `post()`, `put()`, `patch()`, or `delete()` exist anywhere in `src/`.
23
- 3. **All tools annotated** `readOnlyHint: true, destructiveHint: false` — MCP clients surface this to end users.
24
- 4. **Belt-and-braces token scopes** (operator responsibility) — issue the system-user token with read-only scopes (`ads_read`, `pages_read_engagement`, `read_insights`, `instagram_basic`, `instagram_manage_insights`). Even if the server were compromised, the token itself cannot write. See [`ADR-20260421-Read-Only-HTTP-Enforcement.md`](./governance/project-docs/adr/ADR-20260421-Read-Only-HTTP-Enforcement.md).
25
-
26
- Verify any time:
27
-
28
- ```bash
29
- npm run test:readonly
30
- ```
31
-
32
- ---
33
-
34
- ## Tools (36)
35
-
36
- | Domain | Tools |
37
- |---|---|
38
- | **Discovery** (6) | `meta_token_inspect`, `meta_health_check`, `meta_graph_read`, `meta_business_list`, `meta_business_list_assets`, `meta_business_list_system_users` |
39
- | **Ads / Marketing API** (8) | `meta_ads_list_accounts`, `meta_ads_get_account`, `meta_ads_list_campaigns`, `meta_ads_list_adsets`, `meta_ads_list_ads`, `meta_ads_get_insights` ⭐, `meta_ads_get_creative`, `meta_ads_list_custom_audiences` |
40
- | **Pages** (7) | `meta_page_list`, `meta_page_get`, `meta_page_list_posts`, `meta_page_get_post_insights`, `meta_page_get_insights`, `meta_page_list_reviews`, `meta_page_list_videos` |
41
- | **Instagram** (5) | `meta_ig_list_accounts`, `meta_ig_get_account`, `meta_ig_list_media`, `meta_ig_get_media_insights`, `meta_ig_get_audience_demographics` |
42
- | **Pixels** (2) | `meta_pixel_list`, `meta_pixel_get_stats` |
43
- | **Catalog** (3) | `meta_catalog_list`, `meta_catalog_list_products`, `meta_catalog_get_diagnostics` |
44
- | **WhatsApp** (4) | `meta_whatsapp_list_wabas`, `meta_whatsapp_list_phone_numbers`, `meta_whatsapp_list_templates`, `meta_whatsapp_get_analytics` |
45
- | **Eagle's-eye** (1) | `meta_business_overview` ⭐⭐⭐ — single-call consolidated snapshot across the whole business |
46
-
47
- ---
48
-
49
- ## Quickstart
50
-
51
- ### 1. Install
52
-
53
- ```bash
54
- git clone git@github.com:feladeveloper/meta-mcp-server.git
55
- cd meta-mcp-server
56
- npm install
57
- ```
58
-
59
- ### 2. Configure environment
60
-
61
- Copy `.env.example` → `.env` and fill in:
62
-
63
- ```bash
64
- META_ACCESS_TOKEN=... # System-user token (see Token Provisioning below)
65
- META_APP_SECRET=... # App secret of the app that issued the token (Hodusoft app: 193481170220592)
66
- META_API_VERSION=v23.0
67
- META_CACHE_TTL_SECONDS=120
68
- META_MAX_AUTO_PAGES=5
69
- LOG_LEVEL=info
70
- ```
71
-
72
- Optional allowlists (if set, the server refuses to operate on IDs outside the allowlist):
73
-
74
- ```bash
75
- META_ALLOWED_BUSINESS_IDS=133767790806312
76
- META_ALLOWED_AD_ACCOUNT_IDS=act_146517954996436,...
77
- META_ALLOWED_PAGE_IDS=138368686823692,...
78
- META_ALLOWED_IG_USER_IDS=17841406467396631,...
79
- ```
80
-
81
- ### 3. Build and run
82
-
83
- ```bash
84
- npm run build
85
- node dist/index.js # stdio MCP server
86
- ```
87
-
88
- Or in development:
89
-
90
- ```bash
91
- npm run dev
92
- ```
93
-
94
- For an MCP Inspector session against the built server:
95
-
96
- ```bash
97
- npm run inspect
98
- ```
99
-
100
- ### 4. Wire into a client
101
-
102
- Example (Claude Desktop or any MCP client) — fetches the published package on demand via `npx`:
103
-
104
- ```json
105
- {
106
- "mcpServers": {
107
- "meta": {
108
- "command": "npx",
109
- "args": ["-y", "@praise25/meta-mcp-server"],
110
- "env": {
111
- "META_ACCESS_TOKEN": "...",
112
- "META_APP_SECRET": "..."
113
- }
114
- }
115
- }
116
- }
117
- ```
118
-
119
- For a global install (`npm install -g @praise25/meta-mcp-server`), use the bin directly:
120
-
121
- ```json
122
- {
123
- "mcpServers": {
124
- "meta": {
125
- "command": "meta-business-manager-mcp-server",
126
- "env": {
127
- "META_ACCESS_TOKEN": "...",
128
- "META_APP_SECRET": "..."
129
- }
130
- }
131
- }
132
- }
133
- ```
134
-
135
- For local development against a clone of this repo:
136
-
137
- ```json
138
- {
139
- "mcpServers": {
140
- "meta": {
141
- "command": "node",
142
- "args": ["/absolute/path/to/meta-mcp-server/dist/index.js"],
143
- "env": {
144
- "META_ACCESS_TOKEN": "...",
145
- "META_APP_SECRET": "..."
146
- }
147
- }
148
- }
149
- }
150
- ```
151
-
152
- ---
153
-
154
- ## Token Provisioning (Cashtoken-specific)
155
-
156
- The system user `AI_Insights_Reader` (id `122093391782492654`) under the `Hodusoft` business (id `133767790806312`) is the canonical identity for this server. Procedure to issue / rotate its token:
157
-
158
- 1. Business Settings → System Users → `AI_Insights_Reader` → **Generate New Token**
159
- 2. Pick the **Hodusoft** app (id `193481170220592`)
160
- 3. In the scope picker, untick everything, then tick:
161
- - `business_management`, `ads_management` (Meta only exposes management here; server still blocks writes), `pages_read_engagement`, `pages_read_user_content`, `read_insights`, `instagram_basic`, `instagram_manage_insights`, `whatsapp_business_management`, `catalog_management`
162
- 4. Copy the token (Meta only shows it once)
163
- 5. Paste over `META_ACCESS_TOKEN` in `.env`
164
- 6. Verify with `meta_health_check` and `meta_token_inspect`
165
-
166
- Token TTL is ~60 days. Set a calendar reminder; rotation procedure documented in [`/governance/project-docs/runbook.md`](./governance/project-docs/runbook.md).
167
-
168
- See also: [`ADR-20260421-System-User-Token-Pattern.md`](./governance/project-docs/adr/ADR-20260421-System-User-Token-Pattern.md).
169
-
170
- ---
171
-
172
- ## Repository layout
173
-
174
- ```
175
- meta-mcp-server/
176
- ├── CLAUDE.md # AI agent rules (governance)
177
- ├── .cursorrules # Cursor agent rules
178
- ├── .github/
179
- │ ├── copilot-instructions.md # Copilot agent rules
180
- │ ├── ISSUE_TEMPLATE/
181
- │ └── workflows/ # CI/CD (GitHub Actions, Sentinel status check)
182
- ├── .claude/skills/ # 23 governance skills (scaffolding, review, git-ops, …)
183
- ├── .sentinelrc # Sentinel governance plugin config
184
- ├── CHANGELOG.md # SemVer release history
185
- ├── README.md
186
- ├── governance/ # Governance assets — see /governance/standards/
187
- │ ├── standards/ # SDGP policies, coding standards
188
- │ ├── templates/ # Doc templates (specs, ADRs, deviations, project docs)
189
- │ └── project-docs/ # Project documents
190
- │ ├── 1-vision-doc.md
191
- │ ├── 2-brd.md
192
- │ ├── 3-prd.md
193
- │ ├── 5-tad.md
194
- │ ├── runbook.md
195
- │ ├── solution-doc-architecture.md
196
- │ ├── specs/ # Feature specs
197
- │ ├── adr/ # Architecture Decision Records
198
- │ └── deviations/ # Governance deviation logs
199
- ├── src/ # Implementation (see TAD)
200
- │ ├── index.ts # stdio entrypoint
201
- │ ├── server.ts # MCP server wiring
202
- │ ├── config.ts # env + allowlists
203
- │ ├── errors.ts # MetaError, ReadOnlyViolationError
204
- │ ├── logger.ts # pino, stderr only, redacts secrets
205
- │ ├── constants.ts
206
- │ ├── context.ts
207
- │ ├── helpers/
208
- │ │ ├── graph-client.ts # GET-only axios client + retry + cache + appsecret_proof
209
- │ │ ├── cache.ts # LRU TTL cache
210
- │ │ ├── format.ts # JSON / Markdown response formatting
211
- │ │ └── schema.ts # Shared Zod shapes (pagination, date presets, IDs)
212
- │ ├── tools/ # 36 tool implementations grouped by domain
213
- │ │ ├── token/ meta/ business/ ads/ pages/ instagram/ pixels/ catalog/ whatsapp/ overview/
214
- │ │ ├── shared.ts # runList / runGet / errorResult helpers
215
- │ │ └── register.ts # Centralized tool registration
216
- │ └── types/
217
- └── tests/
218
- └── read-only-guard.mjs # Runtime proof that POST/PUT/PATCH/DELETE are blocked
219
- ```
220
-
221
- ---
222
-
223
- ## Governance
224
-
225
- This project is initialized from the [`cashtokenrewards/project-governance-template`](https://github.com/cashtokenrewards/project-governance-template) and follows the **Software Development Governance Policy (SDGP)** in [`/governance/standards/sdgp-main.md`](./governance/standards/sdgp-main.md).
226
-
227
- **Three absolute rules:**
228
- 1. No feature is built without an approved spec. ([`/governance/project-docs/specs/`](./governance/project-docs/specs/))
229
- 2. No ADR is written without a parent feature spec. ([`/governance/project-docs/adr/`](./governance/project-docs/adr/))
230
- 3. No implementation begins without the spec and all required ADRs approved.
231
-
232
- The current implementation (commit zero) was bootstrapped against an initial pass of governance docs:
233
-
234
- | Doc | Path |
235
- |---|---|
236
- | Vision | [`governance/project-docs/1-vision-doc.md`](./governance/project-docs/1-vision-doc.md) |
237
- | BRD | [`governance/project-docs/2-brd.md`](./governance/project-docs/2-brd.md) |
238
- | PRD | [`governance/project-docs/3-prd.md`](./governance/project-docs/3-prd.md) |
239
- | TAD | [`governance/project-docs/5-tad.md`](./governance/project-docs/5-tad.md) |
240
- | Runbook | [`governance/project-docs/runbook.md`](./governance/project-docs/runbook.md) |
241
- | Specs | [`governance/project-docs/specs/`](./governance/project-docs/specs/) |
242
- | ADRs | [`governance/project-docs/adr/`](./governance/project-docs/adr/) |
243
- | Deviations | [`governance/project-docs/deviations/`](./governance/project-docs/deviations/) |
244
-
245
- **Branch model:** Gitflow. `main` (production), `dev` (integration). Short-lived branches: `feature-`, `fix-`, `release-`, `hotfix-`, `docs-`. All merges `--no-ff`. See [`/governance/standards/sdgp-main.md`](./governance/standards/sdgp-main.md) §7.4.
246
-
247
- **AI agent rules:** [`CLAUDE.md`](./CLAUDE.md), [`.cursorrules`](./.cursorrules), [`.github/copilot-instructions.md`](./.github/copilot-instructions.md). Sentinel keeps these in sync with the central governance config.
248
-
249
- ---
250
-
251
- ## Sentinel
252
-
253
- This repository is tracked by the [Sentinel governance plugin](https://github.com/feladeveloper/sentinel-claude-plugin). Configuration lives at [`.sentinelrc`](./.sentinelrc). On any clone:
254
-
255
- ```bash
256
- export SENTINEL_GITHUB_TOKEN="ghp_..." # Personal access token with repo:read
257
- sentinel sync # Pull latest org-level governance into CLAUDE.md
258
- ```
259
-
260
- `/sentinel-sync` and `/sentinel-status` slash commands are also available inside Claude Code once the plugin is installed.
261
-
262
- ---
263
-
264
- ## Scripts
265
-
266
- | Script | What it does |
267
- |---|---|
268
- | `npm run build` | Clean + compile TypeScript → `dist/` |
269
- | `npm run dev` | Run with `tsx` (no build step) |
270
- | `npm run start` | Run built `dist/index.js` |
271
- | `npm run inspect` | Build + open MCP Inspector |
272
- | `npm run check:types` | `tsc --noEmit` |
273
- | `npm run test:readonly` | Build + runtime proof that POST/PUT/PATCH/DELETE are blocked by the Graph client |
274
- | `npm test` | (placeholder for jest suite — see [`SPEC-07-eval-suite.md`](./governance/project-docs/specs/) when added) |
275
-
276
- ---
277
-
278
- ## Status
279
-
280
- | Aspect | State |
281
- |---|---|
282
- | Implementation | **v0.1.0 — bootstrapped, 36 tools, build clean, read-only guard verified** |
283
- | Governance docs | Initial pass — Vision / BRD / PRD / TAD / Runbook / 3 ADRs / 1 deviation drafted |
284
- | App-level (Meta) | Hodusoft app in **development tier** for Marketing API. Standard Access via App Review pending. |
285
- | Asset coverage | All 3 Pages discoverable; 1 Page (CashToken) currently assigned to AI_Insights_Reader; 5 ad accounts visible; 3 Pixels visible; 1 IG (cashtokenhq) discovered via Page link |
286
- | Open scopes | `read_insights`, `instagram_manage_insights`, `whatsapp_business_management`, `catalog_management` may need to be added to the Hodusoft app before they appear in the token picker |
287
-
288
- ---
289
-
290
- ## License
291
-
292
- MIT — see [LICENSE](./LICENSE).
1
+ # meta-mcp-server
2
+
3
+ > **Read-only Model Context Protocol server for Meta Business Manager.** Plug-in for AI assistants ("ChatGPT for marketing insights") that surfaces Pages, Instagram Business, Marketing API ad insights, Pixels, Commerce catalogs, and WhatsApp Business data — all read-only by construction.
4
+
5
+ [![Read-Only Verified](https://img.shields.io/badge/safety-read--only-success)](#read-only-by-construction)
6
+ [![Governance: Cashtoken SDGP](https://img.shields.io/badge/governance-cashtoken%20sdgp-blue)](./governance/standards/sdgp-main.md)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
8
+
9
+ ---
10
+
11
+ ## Why this exists
12
+
13
+ CashToken Marketing operates several Pages, Instagram Business accounts, Meta ad accounts, Pixels and (in future) Catalogs / WhatsApp Business assets across the `Hodusoft` Business Manager. Insight retrieval today is fragmented across Business Suite UIs, Ads Manager exports, and ad-hoc Graph API calls. This MCP server consolidates **read-only** access into a single tool surface that any MCP-aware AI assistant can plug into — turning fragmented UIs into one conversational interface for the marketing team.
14
+
15
+ ---
16
+
17
+ ## Read-only by construction
18
+
19
+ This server cannot write to Meta. Period. Four layers of defence:
20
+
21
+ 1. **HTTP interceptor** — every axios request passes through a guard that throws `ReadOnlyViolationError` if the method isn't `GET`, before the request leaves the process. ([`src/helpers/graph-client.ts`](./src/helpers/graph-client.ts))
22
+ 2. **No write API surface** — the `GraphClient` class exposes only `get()` and `getAllPages()`. No `post()`, `put()`, `patch()`, or `delete()` exist anywhere in `src/`.
23
+ 3. **All tools annotated** `readOnlyHint: true, destructiveHint: false` — MCP clients surface this to end users.
24
+ 4. **Belt-and-braces token scopes** (operator responsibility) — issue the system-user token with read-only scopes (`ads_read`, `pages_read_engagement`, `read_insights`, `instagram_basic`, `instagram_manage_insights`). Even if the server were compromised, the token itself cannot write. See [`ADR-20260421-Read-Only-HTTP-Enforcement.md`](./governance/project-docs/adr/ADR-20260421-Read-Only-HTTP-Enforcement.md).
25
+
26
+ Verify any time:
27
+
28
+ ```bash
29
+ npm run test:readonly
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Tools (36)
35
+
36
+ | Domain | Tools |
37
+ |---|---|
38
+ | **Discovery** (6) | `meta_token_inspect`, `meta_health_check`, `meta_graph_read`, `meta_business_list`, `meta_business_list_assets`, `meta_business_list_system_users` |
39
+ | **Ads / Marketing API** (8) | `meta_ads_list_accounts`, `meta_ads_get_account`, `meta_ads_list_campaigns`, `meta_ads_list_adsets`, `meta_ads_list_ads`, `meta_ads_get_insights` ⭐, `meta_ads_get_creative`, `meta_ads_list_custom_audiences` |
40
+ | **Pages** (7) | `meta_page_list`, `meta_page_get`, `meta_page_list_posts`, `meta_page_get_post_insights`, `meta_page_get_insights`, `meta_page_list_reviews`, `meta_page_list_videos` |
41
+ | **Instagram** (5) | `meta_ig_list_accounts`, `meta_ig_get_account`, `meta_ig_list_media`, `meta_ig_get_media_insights`, `meta_ig_get_audience_demographics` |
42
+ | **Pixels** (2) | `meta_pixel_list`, `meta_pixel_get_stats` |
43
+ | **Catalog** (3) | `meta_catalog_list`, `meta_catalog_list_products`, `meta_catalog_get_diagnostics` |
44
+ | **WhatsApp** (4) | `meta_whatsapp_list_wabas`, `meta_whatsapp_list_phone_numbers`, `meta_whatsapp_list_templates`, `meta_whatsapp_get_analytics` |
45
+ | **Eagle's-eye** (1) | `meta_business_overview` ⭐⭐⭐ — single-call consolidated snapshot across the whole business |
46
+
47
+ ---
48
+
49
+ ## Quickstart
50
+
51
+ ### 1. Install
52
+
53
+ ```bash
54
+ git clone git@github.com:feladeveloper/meta-mcp-server.git
55
+ cd meta-mcp-server
56
+ npm install
57
+ ```
58
+
59
+ ### 2. Configure environment
60
+
61
+ Copy `.env.example` → `.env` and fill in:
62
+
63
+ ```bash
64
+ META_ACCESS_TOKEN=... # System-user token (see Token Provisioning below)
65
+ META_APP_SECRET=... # App secret of the app that issued the token (Hodusoft app: 193481170220592)
66
+ META_API_VERSION=v23.0
67
+ META_CACHE_TTL_SECONDS=120
68
+ META_MAX_AUTO_PAGES=5
69
+ LOG_LEVEL=info
70
+ ```
71
+
72
+ Optional allowlists (if set, the server refuses to operate on IDs outside the allowlist):
73
+
74
+ ```bash
75
+ META_ALLOWED_BUSINESS_IDS=133767790806312
76
+ META_ALLOWED_AD_ACCOUNT_IDS=act_146517954996436,...
77
+ META_ALLOWED_PAGE_IDS=138368686823692,...
78
+ META_ALLOWED_IG_USER_IDS=17841406467396631,...
79
+ ```
80
+
81
+ ### 3. Build and run
82
+
83
+ ```bash
84
+ npm run build
85
+ node dist/index.js # stdio MCP server
86
+ ```
87
+
88
+ Or in development:
89
+
90
+ ```bash
91
+ npm run dev
92
+ ```
93
+
94
+ For an MCP Inspector session against the built server:
95
+
96
+ ```bash
97
+ npm run inspect
98
+ ```
99
+
100
+ ### 4. Wire into a client
101
+
102
+ Example (Claude Desktop or any MCP client) — fetches the published package on demand via `npx`:
103
+
104
+ ```json
105
+ {
106
+ "mcpServers": {
107
+ "meta": {
108
+ "command": "npx",
109
+ "args": ["-y", "@praise25/meta-mcp-server"],
110
+ "env": {
111
+ "META_ACCESS_TOKEN": "...",
112
+ "META_APP_SECRET": "..."
113
+ }
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ For a global install (`npm install -g @praise25/meta-mcp-server`), use the bin directly:
120
+
121
+ ```json
122
+ {
123
+ "mcpServers": {
124
+ "meta": {
125
+ "command": "meta-business-manager-mcp-server",
126
+ "env": {
127
+ "META_ACCESS_TOKEN": "...",
128
+ "META_APP_SECRET": "..."
129
+ }
130
+ }
131
+ }
132
+ }
133
+ ```
134
+
135
+ For local development against a clone of this repo:
136
+
137
+ ```json
138
+ {
139
+ "mcpServers": {
140
+ "meta": {
141
+ "command": "node",
142
+ "args": ["/absolute/path/to/meta-mcp-server/dist/index.js"],
143
+ "env": {
144
+ "META_ACCESS_TOKEN": "...",
145
+ "META_APP_SECRET": "..."
146
+ }
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Token Provisioning (Cashtoken-specific)
155
+
156
+ The system user `AI_Insights_Reader` (id `122093391782492654`) under the `Hodusoft` business (id `133767790806312`) is the canonical identity for this server. Procedure to issue / rotate its token:
157
+
158
+ 1. Business Settings → System Users → `AI_Insights_Reader` → **Generate New Token**
159
+ 2. Pick the **Hodusoft** app (id `193481170220592`)
160
+ 3. In the scope picker, untick everything, then tick:
161
+ - `business_management`, `ads_management` (Meta only exposes management here; server still blocks writes), `pages_read_engagement`, `pages_read_user_content`, `read_insights`, `instagram_basic`, `instagram_manage_insights`, `whatsapp_business_management`, `catalog_management`
162
+ 4. Copy the token (Meta only shows it once)
163
+ 5. Paste over `META_ACCESS_TOKEN` in `.env`
164
+ 6. Verify with `meta_health_check` and `meta_token_inspect`
165
+
166
+ Token TTL is ~60 days. Set a calendar reminder; rotation procedure documented in [`/governance/project-docs/runbook.md`](./governance/project-docs/runbook.md).
167
+
168
+ See also: [`ADR-20260421-System-User-Token-Pattern.md`](./governance/project-docs/adr/ADR-20260421-System-User-Token-Pattern.md).
169
+
170
+ ---
171
+
172
+ ## Repository layout
173
+
174
+ ```
175
+ meta-mcp-server/
176
+ ├── CLAUDE.md # AI agent rules (governance)
177
+ ├── .cursorrules # Cursor agent rules
178
+ ├── .github/
179
+ │ ├── copilot-instructions.md # Copilot agent rules
180
+ │ ├── ISSUE_TEMPLATE/
181
+ │ └── workflows/ # CI/CD (GitHub Actions, Sentinel status check)
182
+ ├── .claude/skills/ # 23 governance skills (scaffolding, review, git-ops, …)
183
+ ├── .sentinelrc # Sentinel governance plugin config
184
+ ├── CHANGELOG.md # SemVer release history
185
+ ├── README.md
186
+ ├── governance/ # Governance assets — see /governance/standards/
187
+ │ ├── standards/ # SDGP policies, coding standards
188
+ │ ├── templates/ # Doc templates (specs, ADRs, deviations, project docs)
189
+ │ └── project-docs/ # Project documents
190
+ │ ├── 1-vision-doc.md
191
+ │ ├── 2-brd.md
192
+ │ ├── 3-prd.md
193
+ │ ├── 5-tad.md
194
+ │ ├── runbook.md
195
+ │ ├── solution-doc-architecture.md
196
+ │ ├── specs/ # Feature specs
197
+ │ ├── adr/ # Architecture Decision Records
198
+ │ └── deviations/ # Governance deviation logs
199
+ ├── src/ # Implementation (see TAD)
200
+ │ ├── index.ts # stdio entrypoint
201
+ │ ├── server.ts # MCP server wiring
202
+ │ ├── config.ts # env + allowlists
203
+ │ ├── errors.ts # MetaError, ReadOnlyViolationError
204
+ │ ├── logger.ts # pino, stderr only, redacts secrets
205
+ │ ├── constants.ts
206
+ │ ├── context.ts
207
+ │ ├── helpers/
208
+ │ │ ├── graph-client.ts # GET-only axios client + retry + cache + appsecret_proof
209
+ │ │ ├── cache.ts # LRU TTL cache
210
+ │ │ ├── format.ts # JSON / Markdown response formatting
211
+ │ │ └── schema.ts # Shared Zod shapes (pagination, date presets, IDs)
212
+ │ ├── tools/ # 36 tool implementations grouped by domain
213
+ │ │ ├── token/ meta/ business/ ads/ pages/ instagram/ pixels/ catalog/ whatsapp/ overview/
214
+ │ │ ├── shared.ts # runList / runGet / errorResult helpers
215
+ │ │ └── register.ts # Centralized tool registration
216
+ │ └── types/
217
+ └── tests/
218
+ └── read-only-guard.mjs # Runtime proof that POST/PUT/PATCH/DELETE are blocked
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Governance
224
+
225
+ This project is initialized from the [`cashtokenrewards/project-governance-template`](https://github.com/cashtokenrewards/project-governance-template) and follows the **Software Development Governance Policy (SDGP)** in [`/governance/standards/sdgp-main.md`](./governance/standards/sdgp-main.md).
226
+
227
+ **Three absolute rules:**
228
+ 1. No feature is built without an approved spec. ([`/governance/project-docs/specs/`](./governance/project-docs/specs/))
229
+ 2. No ADR is written without a parent feature spec. ([`/governance/project-docs/adr/`](./governance/project-docs/adr/))
230
+ 3. No implementation begins without the spec and all required ADRs approved.
231
+
232
+ The current implementation (commit zero) was bootstrapped against an initial pass of governance docs:
233
+
234
+ | Doc | Path |
235
+ |---|---|
236
+ | Vision | [`governance/project-docs/1-vision-doc.md`](./governance/project-docs/1-vision-doc.md) |
237
+ | BRD | [`governance/project-docs/2-brd.md`](./governance/project-docs/2-brd.md) |
238
+ | PRD | [`governance/project-docs/3-prd.md`](./governance/project-docs/3-prd.md) |
239
+ | TAD | [`governance/project-docs/5-tad.md`](./governance/project-docs/5-tad.md) |
240
+ | Runbook | [`governance/project-docs/runbook.md`](./governance/project-docs/runbook.md) |
241
+ | Specs | [`governance/project-docs/specs/`](./governance/project-docs/specs/) |
242
+ | ADRs | [`governance/project-docs/adr/`](./governance/project-docs/adr/) |
243
+ | Deviations | [`governance/project-docs/deviations/`](./governance/project-docs/deviations/) |
244
+
245
+ **Branch model:** Gitflow. `main` (production), `dev` (integration). Short-lived branches: `feature-`, `fix-`, `release-`, `hotfix-`, `docs-`. All merges `--no-ff`. See [`/governance/standards/sdgp-main.md`](./governance/standards/sdgp-main.md) §7.4.
246
+
247
+ **AI agent rules:** [`CLAUDE.md`](./CLAUDE.md), [`.cursorrules`](./.cursorrules), [`.github/copilot-instructions.md`](./.github/copilot-instructions.md). Sentinel keeps these in sync with the central governance config.
248
+
249
+ ---
250
+
251
+ ## Sentinel
252
+
253
+ This repository is tracked by the [Sentinel governance plugin](https://github.com/feladeveloper/sentinel-claude-plugin). Configuration lives at [`.sentinelrc`](./.sentinelrc). On any clone:
254
+
255
+ ```bash
256
+ export SENTINEL_GITHUB_TOKEN="ghp_..." # Personal access token with repo:read
257
+ sentinel sync # Pull latest org-level governance into CLAUDE.md
258
+ ```
259
+
260
+ `/sentinel-sync` and `/sentinel-status` slash commands are also available inside Claude Code once the plugin is installed.
261
+
262
+ ---
263
+
264
+ ## Scripts
265
+
266
+ | Script | What it does |
267
+ |---|---|
268
+ | `npm run build` | Clean + compile TypeScript → `dist/` |
269
+ | `npm run dev` | Run with `tsx` (no build step) |
270
+ | `npm run start` | Run built `dist/index.js` |
271
+ | `npm run inspect` | Build + open MCP Inspector |
272
+ | `npm run check:types` | `tsc --noEmit` |
273
+ | `npm run test:readonly` | Build + runtime proof that POST/PUT/PATCH/DELETE are blocked by the Graph client |
274
+ | `npm test` | (placeholder for jest suite — see [`SPEC-07-eval-suite.md`](./governance/project-docs/specs/) when added) |
275
+
276
+ ---
277
+
278
+ ## Status
279
+
280
+ | Aspect | State |
281
+ |---|---|
282
+ | Implementation | **v0.1.0 — bootstrapped, 36 tools, build clean, read-only guard verified** |
283
+ | Governance docs | Initial pass — Vision / BRD / PRD / TAD / Runbook / 3 ADRs / 1 deviation drafted |
284
+ | App-level (Meta) | Hodusoft app in **development tier** for Marketing API. Standard Access via App Review pending. |
285
+ | Asset coverage | All 3 Pages discoverable; 1 Page (CashToken) currently assigned to AI_Insights_Reader; 5 ad accounts visible; 3 Pixels visible; 1 IG (cashtokenhq) discovered via Page link |
286
+ | Open scopes | `read_insights`, `instagram_manage_insights`, `whatsapp_business_management`, `catalog_management` may need to be added to the Hodusoft app before they appear in the token picker |
287
+
288
+ ---
289
+
290
+ ## License
291
+
292
+ MIT — see [LICENSE](./LICENSE).
@@ -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.2";
7
+ export declare const SERVER_VERSION = "0.1.4";
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.2";
7
+ export const SERVER_VERSION = "0.1.4";
8
8
  export const META_ERROR_CODES = {
9
9
  UNKNOWN: 1,
10
10
  SERVICE_TEMPORARILY_UNAVAILABLE: 2,
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 || code === 200) {
44
- return "Permission denied. Verify the system user is assigned to the target asset and that the app has the required permissions (e.g. ads_read, pages_read_engagement).";
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) {
package/dist/index.js CHANGED
File without changes
@@ -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: {
@@ -26,13 +26,13 @@ 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
- **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
-
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
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.`,
37
37
  inputSchema: inputSchema.shape,
38
38
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
@@ -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, impressions, saves, likes, comments, shares, profile visits, follows-from-post. Requires 'instagram_manage_insights' scope.\n\nDifferent media types support different metric sets:\n- IMAGE/CAROUSEL_ALBUM: impressions, reach, saved, likes, comments, shares, profile_visits\n- VIDEO (Reels): plays, reach, likes, comments, shares, saved, total_interactions\n- STORY: impressions, reach, replies, exits, taps_forward, taps_back (use meta_ig_get_story_insights-style metrics)\n\nIf a metric is unsupported for the media type, Meta returns code 100 retry with a narrower metric list.";
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
- "impressions",
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
- "saved",
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. For Reels prefer 'plays, reach, likes, comments, shares, saved, total_interactions'. Some metrics are media_type-specific if a metric isn't supported for the media type, Meta returns an error."),
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, impressions, saves, likes, comments, shares, profile visits, follows-from-post. Requires 'instagram_manage_insights' scope.
28
-
29
- Different media types support different metric sets:
30
- - IMAGE/CAROUSEL_ALBUM: impressions, reach, saved, likes, comments, shares, profile_visits
31
- - VIDEO (Reels): plays, reach, likes, comments, shares, saved, total_interactions
32
- - STORY: impressions, reach, replies, exits, taps_forward, taps_back (use meta_ig_get_story_insights-style metrics)
33
-
34
- If a metric is unsupported for the media type, Meta returns code 100 — retry with a narrower metric list.`,
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 },
@@ -27,8 +27,8 @@ export const inputSchema = z
27
27
  export const definition = {
28
28
  name: "meta_page_get_post_insights",
29
29
  title: "Get insights for a single Page post",
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
-
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
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.`,
33
33
  inputSchema: inputSchema.shape,
34
34
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
@@ -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 },
package/package.json CHANGED
@@ -1,77 +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.2",
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
- }
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.6",
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
+ }