mcp-meta-ads-incrementality 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,129 @@
1
+ /* ------------------------------------------------------------------------- */
2
+ /* Helpers */
3
+ /* ------------------------------------------------------------------------- */
4
+ /**
5
+ * Normalize a Meta ad account id into the `act_<numeric>` form Meta requires.
6
+ * Accepts strings or numbers, with or without the `act_` prefix.
7
+ */
8
+ export function normalizeAccountId(input) {
9
+ if (input == null) {
10
+ throw new Error('accountId is required');
11
+ }
12
+ const s = typeof input === 'number' ? String(input) : input;
13
+ if (s === '') {
14
+ throw new Error('accountId is required');
15
+ }
16
+ if (s.startsWith('act_')) {
17
+ const rest = s.slice(4);
18
+ if (!/^\d+$/.test(rest)) {
19
+ throw new Error(`accountId must be numeric after the act_ prefix, got "${s}"`);
20
+ }
21
+ return s;
22
+ }
23
+ if (!/^\d+$/.test(s)) {
24
+ throw new Error(`accountId must be numeric or act_-prefixed, got "${s}"`);
25
+ }
26
+ return `act_${s}`;
27
+ }
28
+ function fieldsForLevel(level) {
29
+ const base = ['date_start', 'date_stop', 'spend', 'actions', 'action_values'];
30
+ switch (level) {
31
+ case 'account':
32
+ return base;
33
+ case 'campaign':
34
+ return [...base, 'campaign_id', 'campaign_name'];
35
+ case 'adset':
36
+ return [...base, 'campaign_id', 'campaign_name', 'adset_id', 'adset_name'];
37
+ case 'ad':
38
+ return [
39
+ ...base,
40
+ 'campaign_id',
41
+ 'campaign_name',
42
+ 'adset_id',
43
+ 'adset_name',
44
+ 'ad_id',
45
+ 'ad_name',
46
+ ];
47
+ }
48
+ }
49
+ /* ------------------------------------------------------------------------- */
50
+ /* MetaInsightsClient */
51
+ /* ------------------------------------------------------------------------- */
52
+ export class MetaInsightsClient {
53
+ sdk;
54
+ constructor(sdk, accessToken) {
55
+ this.sdk = sdk;
56
+ if (!accessToken) {
57
+ throw new Error('accessToken is required');
58
+ }
59
+ sdk.init(accessToken);
60
+ }
61
+ async getInsights(input) {
62
+ const accountId = normalizeAccountId(input.accountId);
63
+ const account = this.sdk.createAdAccount(accountId);
64
+ const fields = fieldsForLevel(input.level);
65
+ const params = {
66
+ level: input.level,
67
+ time_range: { since: input.dateRange.since, until: input.dateRange.until },
68
+ action_attribution_windows: [...input.attributionWindows],
69
+ use_unified_attribution_setting: true,
70
+ };
71
+ if (input.breakdowns && input.breakdowns.length > 0) {
72
+ params.breakdowns = [...input.breakdowns];
73
+ }
74
+ const filtering = [];
75
+ if (input.campaignId) {
76
+ filtering.push({ field: 'campaign.id', operator: 'EQUAL', value: input.campaignId });
77
+ }
78
+ if (input.adsetId) {
79
+ filtering.push({ field: 'adset.id', operator: 'EQUAL', value: input.adsetId });
80
+ }
81
+ if (filtering.length > 0) {
82
+ params.filtering = filtering;
83
+ }
84
+ const cursor = await account.getInsights(fields, params);
85
+ const rows = [];
86
+ let page = cursor;
87
+ while (page !== null) {
88
+ for (const item of page) {
89
+ rows.push(item._data ?? item);
90
+ }
91
+ if (page.hasNext()) {
92
+ await page.next();
93
+ // Cursor mutates in place — same reference, new page contents.
94
+ }
95
+ else {
96
+ page = null;
97
+ }
98
+ }
99
+ return rows;
100
+ }
101
+ }
102
+ /* ------------------------------------------------------------------------- */
103
+ /* Default SDK adapter — wraps facebook-nodejs-business-sdk */
104
+ /* ------------------------------------------------------------------------- */
105
+ /**
106
+ * Create the production `MetaSdkLike` adapter that delegates to the real
107
+ * `facebook-nodejs-business-sdk`. Imported lazily so unit tests that inject
108
+ * fakes never have to load the SDK.
109
+ */
110
+ export async function createDefaultMetaSdk() {
111
+ const sdkModule = await import('facebook-nodejs-business-sdk');
112
+ return {
113
+ init(token) {
114
+ sdkModule.FacebookAdsApi.init(token);
115
+ },
116
+ createAdAccount(accountId) {
117
+ const account = new sdkModule.AdAccount(accountId);
118
+ return {
119
+ async getInsights(fields, params) {
120
+ // SDK returns Cursor | Promise<Cursor>; await normalizes both.
121
+ // Type cast bridges the SDK's loose Record<string, any> items to our SdkCursor shape.
122
+ const cursor = await account.getInsights(fields, params);
123
+ return cursor;
124
+ },
125
+ };
126
+ },
127
+ };
128
+ }
129
+ //# sourceMappingURL=meta-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta-client.js","sourceRoot":"","sources":["../../src/lib/meta-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare const configDir: string;
2
+ export declare const credentialsFilePath: string;
3
+ export declare function openBrowser(url: string): Promise<void>;
4
+ export declare function findFreeLoopbackPort(): Promise<number>;
5
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,SAAS,QAAe,CAAC;AACtC,eAAO,MAAM,mBAAmB,QAA8C,CAAC;AAE/E,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5D;AAKD,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAS5D"}
@@ -0,0 +1,34 @@
1
+ // ============================================
2
+ // CROSS-PLATFORM DISPATCH HELPERS
3
+ // ============================================
4
+ import envPaths from "env-paths";
5
+ import openModule from "open";
6
+ import { createServer } from "net";
7
+ import path from "path";
8
+ const paths = envPaths("mcp-meta-ads", { suffix: "nodejs" });
9
+ export const configDir = paths.config;
10
+ export const credentialsFilePath = path.join(paths.config, "credentials.json");
11
+ export async function openBrowser(url) {
12
+ await openModule(url);
13
+ }
14
+ const LOOPBACK_PORT_RANGE_START = 8085;
15
+ const LOOPBACK_PORT_RANGE_END = 8199;
16
+ export async function findFreeLoopbackPort() {
17
+ for (let port = LOOPBACK_PORT_RANGE_START; port <= LOOPBACK_PORT_RANGE_END; port++) {
18
+ if (await isPortFree(port)) {
19
+ return port;
20
+ }
21
+ }
22
+ throw new Error(`No free port found in range ${LOOPBACK_PORT_RANGE_START}-${LOOPBACK_PORT_RANGE_END}.`);
23
+ }
24
+ function isPortFree(port) {
25
+ return new Promise((resolve) => {
26
+ const server = createServer();
27
+ server.once("error", () => resolve(false));
28
+ server.once("listening", () => {
29
+ server.close(() => resolve(true));
30
+ });
31
+ server.listen(port, "127.0.0.1");
32
+ });
33
+ }
34
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,kCAAkC;AAClC,+CAA+C;AAE/C,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,UAAU,MAAM,MAAM,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC;AAEnC,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,KAAK,GAAG,QAAQ,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;AAE7D,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC;AACtC,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAE/E,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW;IAC3C,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,yBAAyB,GAAG,IAAI,CAAC;AACvC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAErC,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,KAAK,IAAI,IAAI,GAAG,yBAAyB,EAAE,IAAI,IAAI,uBAAuB,EAAE,IAAI,EAAE,EAAE,CAAC;QACnF,IAAI,MAAM,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CACb,+BAA+B,yBAAyB,IAAI,uBAAuB,GAAG,CACvF,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;YAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,86 @@
1
+ import { z } from 'zod';
2
+ import { type AttributionWindow } from '../lib/attribution.js';
3
+ import { type ExtractedConversion, type MetaInsightsRow } from '../lib/insights.js';
4
+ import type { GetInsightsParams, InsightsLevel } from '../lib/meta-client.js';
5
+ export declare const insightsIncrementalityInputSchema: z.ZodObject<{
6
+ ad_account_id: z.ZodString;
7
+ date_range: z.ZodObject<{
8
+ since: z.ZodString;
9
+ until: z.ZodString;
10
+ }, "strip", z.ZodTypeAny, {
11
+ since: string;
12
+ until: string;
13
+ }, {
14
+ since: string;
15
+ until: string;
16
+ }>;
17
+ conversion_event_name: z.ZodString;
18
+ attribution_windows: z.ZodEffects<z.ZodOptional<z.ZodArray<z.ZodEnum<["incrementality", "1d_click", "7d_click", "28d_click", "1d_view", "1d_ev", "dda", "skan_view", "skan_click"]>, "many">>, ("incrementality" | "1d_click" | "7d_click" | "28d_click" | "1d_view" | "1d_ev" | "dda" | "skan_view" | "skan_click")[], ("incrementality" | "1d_click" | "7d_click" | "28d_click" | "1d_view" | "1d_ev" | "dda" | "skan_view" | "skan_click")[] | undefined>;
19
+ level: z.ZodDefault<z.ZodOptional<z.ZodEnum<["account", "campaign", "adset", "ad"]>>>;
20
+ campaign_id: z.ZodOptional<z.ZodString>;
21
+ adset_id: z.ZodOptional<z.ZodString>;
22
+ breakdowns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
23
+ }, "strip", z.ZodTypeAny, {
24
+ level: "account" | "campaign" | "adset" | "ad";
25
+ ad_account_id: string;
26
+ date_range: {
27
+ since: string;
28
+ until: string;
29
+ };
30
+ conversion_event_name: string;
31
+ attribution_windows: ("incrementality" | "1d_click" | "7d_click" | "28d_click" | "1d_view" | "1d_ev" | "dda" | "skan_view" | "skan_click")[];
32
+ campaign_id?: string | undefined;
33
+ adset_id?: string | undefined;
34
+ breakdowns?: string[] | undefined;
35
+ }, {
36
+ ad_account_id: string;
37
+ date_range: {
38
+ since: string;
39
+ until: string;
40
+ };
41
+ conversion_event_name: string;
42
+ campaign_id?: string | undefined;
43
+ adset_id?: string | undefined;
44
+ level?: "account" | "campaign" | "adset" | "ad" | undefined;
45
+ breakdowns?: string[] | undefined;
46
+ attribution_windows?: ("incrementality" | "1d_click" | "7d_click" | "28d_click" | "1d_view" | "1d_ev" | "dda" | "skan_view" | "skan_click")[] | undefined;
47
+ }>;
48
+ export type InsightsIncrementalityInput = z.infer<typeof insightsIncrementalityInputSchema>;
49
+ export interface InsightsIncrementalityRow extends ExtractedConversion {
50
+ /** Sum of 7d_click + 1d_view (whichever are present), the "default attributed" number agencies report. Null when neither is requested. */
51
+ default_attributed: number | null;
52
+ /** default_attributed / incrementality. Null when incrementality is 0 or default not requested. */
53
+ inflation_factor: number | null;
54
+ /** (default_attributed - incrementality) / default_attributed * 100. Null in the same cases as inflation_factor. */
55
+ overstatement_pct: number | null;
56
+ }
57
+ export interface InsightsIncrementalitySummary {
58
+ total_spend: number;
59
+ total_conversions: Record<string, number>;
60
+ cpa: Record<string, number | null>;
61
+ /** Computed from totals (not row averages): total_default_attributed / total_incrementality. Null if incrementality total is 0 or default not requested. */
62
+ inflation_factor: number | null;
63
+ }
64
+ export interface InsightsIncrementalityResult {
65
+ meta: {
66
+ ad_account_id: string;
67
+ date_range: {
68
+ since: string;
69
+ until: string;
70
+ };
71
+ level: InsightsLevel;
72
+ conversion_event_name: string;
73
+ attribution_windows: AttributionWindow[];
74
+ };
75
+ rows: InsightsIncrementalityRow[];
76
+ summary: InsightsIncrementalitySummary;
77
+ }
78
+ /** Minimal interface this tool needs from a Meta insights client. */
79
+ export interface InsightsClient {
80
+ getInsights(params: GetInsightsParams): Promise<MetaInsightsRow[]>;
81
+ }
82
+ export interface InsightsIncrementalityDeps {
83
+ metaClient: InsightsClient;
84
+ }
85
+ export declare function runInsightsIncrementality(rawInput: unknown, deps: InsightsIncrementalityDeps): Promise<InsightsIncrementalityResult>;
86
+ //# sourceMappingURL=insights-incrementality.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insights-incrementality.d.ts","sourceRoot":"","sources":["../../src/tools/insights-incrementality.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAe9E,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAY5C,CAAC;AAEH,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAC/C,OAAO,iCAAiC,CACzC,CAAC;AAMF,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACpE,0IAA0I;IAC1I,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,mGAAmG;IACnG,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,oHAAoH;IACpH,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,6BAA6B;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACnC,4JAA4J;IAC5J,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE;QACJ,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAC7C,KAAK,EAAE,aAAa,CAAC;QACrB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,mBAAmB,EAAE,iBAAiB,EAAE,CAAC;KAC1C,CAAC;IACF,IAAI,EAAE,yBAAyB,EAAE,CAAC;IAClC,OAAO,EAAE,6BAA6B,CAAC;CACxC;AAMD,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;CACpE;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,cAAc,CAAC;CAC5B;AA4CD,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,0BAA0B,GAC/B,OAAO,CAAC,4BAA4B,CAAC,CAwDvC"}
@@ -0,0 +1,102 @@
1
+ import { z } from 'zod';
2
+ import { AttributionWindowSchema, parseAttributionWindows, } from '../lib/attribution.js';
3
+ import { extractConversionRows, computeInflationFactor, } from '../lib/insights.js';
4
+ /* ------------------------------------------------------------------------- */
5
+ /* Input schema */
6
+ /* ------------------------------------------------------------------------- */
7
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
8
+ const dateRangeSchema = z.object({
9
+ since: z.string().regex(ISO_DATE, 'date must be YYYY-MM-DD'),
10
+ until: z.string().regex(ISO_DATE, 'date must be YYYY-MM-DD'),
11
+ });
12
+ const levelSchema = z.enum(['account', 'campaign', 'adset', 'ad']);
13
+ export const insightsIncrementalityInputSchema = z.object({
14
+ ad_account_id: z.string().min(1, 'ad_account_id is required'),
15
+ date_range: dateRangeSchema,
16
+ conversion_event_name: z.string().min(1, 'conversion_event_name is required'),
17
+ attribution_windows: z
18
+ .array(AttributionWindowSchema)
19
+ .optional()
20
+ .transform((v) => parseAttributionWindows(v)),
21
+ level: levelSchema.optional().default('campaign'),
22
+ campaign_id: z.string().optional(),
23
+ adset_id: z.string().optional(),
24
+ breakdowns: z.array(z.string()).optional(),
25
+ });
26
+ /* ------------------------------------------------------------------------- */
27
+ /* Implementation */
28
+ /* ------------------------------------------------------------------------- */
29
+ const DEFAULT_AGENCY_WINDOWS = ['7d_click', '1d_view'];
30
+ function computeRowComparison(conversions, windows) {
31
+ const hasIncrementality = windows.includes('incrementality');
32
+ const defaultParts = DEFAULT_AGENCY_WINDOWS.filter((w) => windows.includes(w));
33
+ if (!hasIncrementality || defaultParts.length === 0) {
34
+ return { default_attributed: null, inflation_factor: null, overstatement_pct: null };
35
+ }
36
+ const defaultAttributed = defaultParts.reduce((sum, w) => sum + (conversions[w] ?? 0), 0);
37
+ const incrementality = conversions.incrementality ?? 0;
38
+ const inflation = computeInflationFactor({
39
+ incrementality,
40
+ comparison: defaultAttributed,
41
+ });
42
+ const overstatement = inflation == null || defaultAttributed === 0
43
+ ? null
44
+ : ((defaultAttributed - incrementality) / defaultAttributed) * 100;
45
+ return {
46
+ default_attributed: defaultAttributed,
47
+ inflation_factor: inflation,
48
+ overstatement_pct: overstatement,
49
+ };
50
+ }
51
+ export async function runInsightsIncrementality(rawInput, deps) {
52
+ const input = insightsIncrementalityInputSchema.parse(rawInput);
53
+ const windows = input.attribution_windows;
54
+ const fetchParams = {
55
+ accountId: input.ad_account_id,
56
+ dateRange: input.date_range,
57
+ attributionWindows: windows,
58
+ level: input.level,
59
+ ...(input.campaign_id ? { campaignId: input.campaign_id } : {}),
60
+ ...(input.adset_id ? { adsetId: input.adset_id } : {}),
61
+ ...(input.breakdowns ? { breakdowns: input.breakdowns } : {}),
62
+ };
63
+ const rawRows = await deps.metaClient.getInsights(fetchParams);
64
+ const extracted = extractConversionRows(rawRows, input.conversion_event_name, windows);
65
+ const rows = extracted.map((row) => ({
66
+ ...row,
67
+ ...computeRowComparison(row.conversions, windows),
68
+ }));
69
+ // Summary aggregation across rows
70
+ const totalSpend = rows.reduce((sum, r) => sum + (r.spend ?? 0), 0);
71
+ const totalConversions = {};
72
+ for (const w of windows)
73
+ totalConversions[w] = 0;
74
+ for (const r of rows) {
75
+ for (const w of windows) {
76
+ totalConversions[w] = (totalConversions[w] ?? 0) + (r.conversions[w] ?? 0);
77
+ }
78
+ }
79
+ const cpa = {};
80
+ for (const w of windows) {
81
+ const c = totalConversions[w] ?? 0;
82
+ cpa[w] = c > 0 ? totalSpend / c : null;
83
+ }
84
+ const summaryComparison = computeRowComparison(totalConversions, windows);
85
+ return {
86
+ meta: {
87
+ ad_account_id: input.ad_account_id,
88
+ date_range: input.date_range,
89
+ level: input.level,
90
+ conversion_event_name: input.conversion_event_name,
91
+ attribution_windows: [...windows],
92
+ },
93
+ rows,
94
+ summary: {
95
+ total_spend: totalSpend,
96
+ total_conversions: totalConversions,
97
+ cpa,
98
+ inflation_factor: summaryComparison.inflation_factor,
99
+ },
100
+ };
101
+ }
102
+ //# sourceMappingURL=insights-incrementality.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insights-incrementality.js","sourceRoot":"","sources":["../../src/tools/insights-incrementality.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "mcp-meta-ads-incrementality",
3
+ "version": "0.2.0",
4
+ "description": "MCP server for Meta Marketing API focused on incrementality reporting. Surfaces Meta's incremental conversion attribution alongside default 7d-click/1d-view and dda windows so agencies can see the gap between reported and incremental performance.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "mcp-meta-ads-incrementality": "dist/index.js",
8
+ "mcp-meta-ads-auth": "dist/auth-cli.js"
9
+ },
10
+ "type": "module",
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "start": "node dist/index.js",
19
+ "dev": "tsx src/index.ts",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest"
22
+ },
23
+ "keywords": [
24
+ "mcp",
25
+ "model-context-protocol",
26
+ "meta-ads",
27
+ "facebook-ads",
28
+ "marketing-api",
29
+ "incrementality",
30
+ "attribution",
31
+ "advertising",
32
+ "agency"
33
+ ],
34
+ "author": "drak-marketing",
35
+ "license": "MIT",
36
+ "engines": {
37
+ "node": ">=18.18.0"
38
+ },
39
+ "dependencies": {
40
+ "@modelcontextprotocol/sdk": "^0.5.0",
41
+ "env-paths": "^3.0.0",
42
+ "facebook-nodejs-business-sdk": "^24.0.1",
43
+ "open": "^10.0.0",
44
+ "zod": "^3.22.4"
45
+ },
46
+ "devDependencies": {
47
+ "@types/facebook-nodejs-business-sdk": "^23.0.0",
48
+ "@types/node": "^20.10.0",
49
+ "tsx": "^4.7.0",
50
+ "typescript": "^5.3.0",
51
+ "vitest": "^4.0.18"
52
+ }
53
+ }