@praise25/meta-mcp-server 0.1.0 → 0.1.1
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/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/helpers/validate.d.ts +26 -0
- package/dist/helpers/validate.js +82 -0
- package/dist/tools/register.js +12 -1
- package/package.json +4 -2
package/dist/constants.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare const CHARACTER_LIMIT = 25000;
|
|
|
4
4
|
export declare const DEFAULT_PAGE_LIMIT = 25;
|
|
5
5
|
export declare const MAX_PAGE_LIMIT = 500;
|
|
6
6
|
export declare const SERVER_NAME = "meta-business-manager-mcp-server";
|
|
7
|
-
export declare const SERVER_VERSION = "0.1.
|
|
7
|
+
export declare const SERVER_VERSION = "0.1.1";
|
|
8
8
|
export declare const META_ERROR_CODES: {
|
|
9
9
|
readonly UNKNOWN: 1;
|
|
10
10
|
readonly SERVICE_TEMPORARILY_UNAVAILABLE: 2;
|
package/dist/constants.js
CHANGED
|
@@ -4,7 +4,7 @@ export const CHARACTER_LIMIT = 25_000;
|
|
|
4
4
|
export const DEFAULT_PAGE_LIMIT = 25;
|
|
5
5
|
export const MAX_PAGE_LIMIT = 500;
|
|
6
6
|
export const SERVER_NAME = "meta-business-manager-mcp-server";
|
|
7
|
-
export const SERVER_VERSION = "0.1.
|
|
7
|
+
export const SERVER_VERSION = "0.1.1";
|
|
8
8
|
export const META_ERROR_CODES = {
|
|
9
9
|
UNKNOWN: 1,
|
|
10
10
|
SERVICE_TEMPORARILY_UNAVAILABLE: 2,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z, type ZodTypeAny } from "zod";
|
|
2
|
+
import { type ToolTextResult } from "./format.js";
|
|
3
|
+
/** Walk an arbitrary value and collect any string leaves that look like placeholders. */
|
|
4
|
+
export declare function findPlaceholders(value: unknown, path?: (string | number)[]): {
|
|
5
|
+
path: (string | number)[];
|
|
6
|
+
value: string;
|
|
7
|
+
}[];
|
|
8
|
+
/**
|
|
9
|
+
* Defence-in-depth input validator. Wraps a Zod object schema and:
|
|
10
|
+
* 1. Detects placeholder strings (e.g. literal "<YOUR_PAGE_ID>") with a
|
|
11
|
+
* friendly tool error explaining the AI must substitute real values.
|
|
12
|
+
* 2. Runs full Zod validation including refinements (regex, min/max, etc.).
|
|
13
|
+
* 3. Returns a structured ToolError on failure rather than throwing,
|
|
14
|
+
* so the AI gets actionable feedback instead of an opaque crash.
|
|
15
|
+
*
|
|
16
|
+
* Why this exists: some MCP gateways forward inputs to handlers without
|
|
17
|
+
* enforcing per-property Zod refinements. Without this guard, a placeholder
|
|
18
|
+
* like "act_<YOUR_PAGE_ID>" would reach Meta's API and waste a round trip.
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateInput<T extends ZodTypeAny>(schema: T, args: unknown): {
|
|
21
|
+
ok: true;
|
|
22
|
+
data: z.infer<T>;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
error: ToolTextResult;
|
|
26
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { toolError } from "./format.js";
|
|
2
|
+
/**
|
|
3
|
+
* Heuristics for spotting placeholder strings the AI may pass without
|
|
4
|
+
* substituting a real value:
|
|
5
|
+
* - Anything wrapped in angle brackets, e.g. "<YOUR_PAGE_ID>", "<id>"
|
|
6
|
+
* - Strings starting with `YOUR_`, `MY_`, `EXAMPLE_`
|
|
7
|
+
* - Strings ending with `_HERE`, `_PLACEHOLDER`
|
|
8
|
+
* - The literal strings "string", "id", "number" (when inside a regex slot)
|
|
9
|
+
*/
|
|
10
|
+
const PLACEHOLDER_PATTERNS = [
|
|
11
|
+
/<[^>]{1,80}>/,
|
|
12
|
+
/^YOUR[_\s-]/i,
|
|
13
|
+
/^MY[_\s-]/i,
|
|
14
|
+
/^EXAMPLE[_\s-]/i,
|
|
15
|
+
/[_\s-]HERE$/i,
|
|
16
|
+
/[_\s-]PLACEHOLDER$/i,
|
|
17
|
+
/^TODO$/i,
|
|
18
|
+
/^FIXME$/i,
|
|
19
|
+
];
|
|
20
|
+
/** Walk an arbitrary value and collect any string leaves that look like placeholders. */
|
|
21
|
+
export function findPlaceholders(value, path = []) {
|
|
22
|
+
const hits = [];
|
|
23
|
+
if (value == null)
|
|
24
|
+
return hits;
|
|
25
|
+
if (typeof value === "string") {
|
|
26
|
+
if (PLACEHOLDER_PATTERNS.some((rx) => rx.test(value))) {
|
|
27
|
+
hits.push({ path, value });
|
|
28
|
+
}
|
|
29
|
+
return hits;
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
value.forEach((v, i) => hits.push(...findPlaceholders(v, [...path, i])));
|
|
33
|
+
return hits;
|
|
34
|
+
}
|
|
35
|
+
if (typeof value === "object") {
|
|
36
|
+
for (const [k, v] of Object.entries(value)) {
|
|
37
|
+
hits.push(...findPlaceholders(v, [...path, k]));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return hits;
|
|
41
|
+
}
|
|
42
|
+
function formatPath(p) {
|
|
43
|
+
return p.length ? p.join(".") : "(root)";
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Defence-in-depth input validator. Wraps a Zod object schema and:
|
|
47
|
+
* 1. Detects placeholder strings (e.g. literal "<YOUR_PAGE_ID>") with a
|
|
48
|
+
* friendly tool error explaining the AI must substitute real values.
|
|
49
|
+
* 2. Runs full Zod validation including refinements (regex, min/max, etc.).
|
|
50
|
+
* 3. Returns a structured ToolError on failure rather than throwing,
|
|
51
|
+
* so the AI gets actionable feedback instead of an opaque crash.
|
|
52
|
+
*
|
|
53
|
+
* Why this exists: some MCP gateways forward inputs to handlers without
|
|
54
|
+
* enforcing per-property Zod refinements. Without this guard, a placeholder
|
|
55
|
+
* like "act_<YOUR_PAGE_ID>" would reach Meta's API and waste a round trip.
|
|
56
|
+
*/
|
|
57
|
+
export function validateInput(schema, args) {
|
|
58
|
+
const placeholders = findPlaceholders(args);
|
|
59
|
+
if (placeholders.length > 0) {
|
|
60
|
+
const summary = placeholders
|
|
61
|
+
.map((p) => `${formatPath(p.path)} = ${JSON.stringify(p.value)}`)
|
|
62
|
+
.join("; ");
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
error: toolError(`Input contains placeholder values that were not substituted: ${summary}`, `Replace placeholders with real IDs / values before retrying. Examples for this server: business_id "133767790806312"; ad_account_id "act_146517954996436"; page_id "138368686823692". If you don't know the correct ID, call meta_business_list_assets first to discover available assets.`, { placeholders }),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const parsed = schema.safeParse(args);
|
|
69
|
+
if (!parsed.success) {
|
|
70
|
+
const issues = parsed.error.issues.map((iss) => ({
|
|
71
|
+
path: formatPath(iss.path),
|
|
72
|
+
message: iss.message,
|
|
73
|
+
code: iss.code,
|
|
74
|
+
}));
|
|
75
|
+
const summary = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
error: toolError(`Invalid input: ${summary}`, `Each parameter has its own format requirement (regex, enum, min/max). See the tool's inputSchema description for the exact expectations.`, { issues }),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return { ok: true, data: parsed.data };
|
|
82
|
+
}
|
package/dist/tools/register.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { validateInput } from "../helpers/validate.js";
|
|
1
3
|
// Token + meta
|
|
2
4
|
import * as tokenInspect from "./token/inspect.js";
|
|
3
5
|
import * as tokenHealth from "./token/health.js";
|
|
@@ -74,7 +76,16 @@ export function registerTools(server, ctx) {
|
|
|
74
76
|
inputSchema: mod.definition.inputSchema,
|
|
75
77
|
annotations: mod.definition.annotations,
|
|
76
78
|
}, (async (args) => {
|
|
77
|
-
|
|
79
|
+
// Defense in depth: re-validate at the handler boundary even if the
|
|
80
|
+
// SDK / MCP gateway already did. Some gateways forward inputs without
|
|
81
|
+
// enforcing per-property Zod refinements (e.g. regex on ad_account_id),
|
|
82
|
+
// which would otherwise let placeholder strings like "act_<YOUR_PAGE_ID>"
|
|
83
|
+
// reach Meta. See ADR-20260429-Handler-Input-Validation.md.
|
|
84
|
+
const validated = validateInput(mod.inputSchema ?? z.object(mod.definition.inputSchema), args);
|
|
85
|
+
if (!validated.ok) {
|
|
86
|
+
return validated.error;
|
|
87
|
+
}
|
|
88
|
+
return mod.handler(validated.data, ctx);
|
|
78
89
|
}));
|
|
79
90
|
names.push(mod.definition.name);
|
|
80
91
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praise25/meta-mcp-server",
|
|
3
3
|
"description": "Read-only Model Context Protocol server for Meta Business Manager — Pages, Instagram, Ads insights, Pixels, Catalog, WhatsApp.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.1",
|
|
5
5
|
"author": "Stephen A.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/feladeveloper/meta-mcp-server#readme",
|
|
@@ -52,8 +52,10 @@
|
|
|
52
52
|
"inspect": "npm run build && npx @modelcontextprotocol/inspector dist/index.js",
|
|
53
53
|
"check:types": "tsc --noEmit --project tsconfig.json",
|
|
54
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",
|
|
55
57
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
56
|
-
"prepublishOnly": "npm run check:types && npm run test:
|
|
58
|
+
"prepublishOnly": "npm run check:types && npm run test:invariants"
|
|
57
59
|
},
|
|
58
60
|
"dependencies": {
|
|
59
61
|
"@modelcontextprotocol/sdk": "^1.11.2",
|