@mambalabsdev/mcp-meta-brand-presence-mapper 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mamba Labs
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 ADDED
@@ -0,0 +1,86 @@
1
+ # Instagram Threads Facebook Brand Presence Mapper MCP Server
2
+
3
+ [![Smithery](https://smithery.ai/badge/mambabuilt/mcp-meta-brand-presence-mapper)](https://smithery.ai/servers/mambabuilt/mcp-meta-brand-presence-mapper) [![Glama score](https://glama.ai/mcp/servers/mambalabsdev/mcp-meta-brand-presence-mapper/badges/score.svg)](https://glama.ai/mcp/servers/mambalabsdev/mcp-meta-brand-presence-mapper) [![npm version](https://img.shields.io/npm/v/@mambalabsdev/mcp-meta-brand-presence-mapper)](https://www.npmjs.com/package/@mambalabsdev/mcp-meta-brand-presence-mapper) [![npm downloads](https://img.shields.io/npm/dm/@mambalabsdev/mcp-meta-brand-presence-mapper)](https://www.npmjs.com/package/@mambalabsdev/mcp-meta-brand-presence-mapper) [![license](https://img.shields.io/github/license/mambalabsdev/mcp-meta-brand-presence-mapper)](https://github.com/mambalabsdev/mcp-meta-brand-presence-mapper/blob/main/LICENSE)
4
+
5
+ MCP server for the Mamba Labs **Instagram Threads Facebook Brand Presence Mapper** actor on Apify.
6
+
7
+ Resolve a company domain to its Instagram, Threads and Facebook accounts with follower and post counts.
8
+
9
+ ## What it does
10
+
11
+ Resolve a company domain to its Instagram, Threads and Facebook accounts with follower and post counts, as one flat Clay ready row. The Threads handle is derived from the resolved Instagram handle at no extra discovery cost. Instagram and Threads counts are rounded by Meta and the row carries both the rounded integer and the platform's own display string. Facebook is best effort: Meta serves a login wall to anonymous clients, so blocked is a normal answer there and never a zero. Read only; requires an APIFY_TOKEN and consumes Apify credits per call.
12
+
13
+ ## Quick start
14
+
15
+ Add this to your MCP client configuration:
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "mamba-meta-brand-presence-mapper": {
21
+ "command": "npx",
22
+ "args": ["-y", "@mambalabsdev/mcp-meta-brand-presence-mapper"],
23
+ "env": { "APIFY_TOKEN": "your-apify-token" }
24
+ }
25
+ }
26
+ }
27
+ ```
28
+
29
+ ## Prerequisites
30
+
31
+ - Node.js 18 or newer
32
+ - An Apify API token from [console.apify.com/account/integrations](https://console.apify.com/account/integrations)
33
+
34
+ The actor is pay per event and consumes Apify credits per call. Pricing is on the
35
+ [actor page](https://apify.com/mambalabs/meta-brand-presence-mapper).
36
+
37
+ ## Example prompts
38
+
39
+ - "Get the Instagram and Threads follower counts for glossier.com."
40
+ - "Has oatly.com adopted Threads yet, and how many posts do they have?"
41
+ - "Find the Facebook Page for everlane.com."
42
+
43
+ ## Tool and inputs
44
+
45
+ Tool: `map_meta_brand_presence`
46
+
47
+ | Input | Type | Meaning |
48
+ |---|---|---|
49
+ | `company_domain` | string | Bare company domain, for example shopify.com. Supply this or a handle. With a domain the actor runs full discovery; with a handle it skips straight to |
50
+ | `company_name` | string | Optional. Improves search accuracy and is what the identity gate checks a discovered profile against, so supplying it reduces wrong matches. |
51
+ | `handle` | string | Optional. The Instagram handle with or without the leading @. Supplying it skips Instagram discovery AND gives Threads its handle for free, because Th |
52
+ | `platforms` | array | Which of the three Meta surfaces to map. Default is all three. Dropping Facebook is the common choice: it is the least reliable of the three and it co |
53
+ | `includeFollowerCounts` | boolean | When "true" (default) the profile page is fetched and the counts are extracted. Set "false" to resolve the profile URL only, which is cheaper and need |
54
+ | `skipCache` | boolean | When "false" (default) a successful lookup is cached for seven days and reused. Set "true" to force a fresh fetch. Sent as a string for Clay compatibi |
55
+
56
+ ## Reading the output
57
+
58
+ Every row carries a per platform `_status` field, and it is the field to read
59
+ first. The vocabulary is the same across the whole Mamba Labs social family:
60
+
61
+ | Status | Meaning |
62
+ |---|---|
63
+ | `ok` | fetched and parsed, the value is there |
64
+ | `not_found` | we looked and there is no such profile |
65
+ | `not_extractable` | the profile exists and the value is not on the wire to us |
66
+ | `blocked` | the platform refused us, worth retrying later |
67
+ | `identity_mismatch` | we found a real profile and it belongs to someone else |
68
+ | `skipped` | you did not ask for this platform |
69
+
70
+ **`false` and `null` are never interchangeable.** `false` means we looked and the
71
+ answer is no. `null` means we could not look. If you filter for companies with no
72
+ presence, filter on `false`, because `null` rows are unknown rather than absent.
73
+
74
+ ## Full actor documentation
75
+
76
+ [apify.com/mambalabs/meta-brand-presence-mapper](https://apify.com/mambalabs/meta-brand-presence-mapper)
77
+
78
+ ## Mamba Labs GTM Suite
79
+
80
+ Mamba Labs builds a fleet of GTM enrichment actors that share one flat, Clay
81
+ ready output convention, so their rows join on `company_domain` with no cleaning
82
+ step. Full fleet: [apify.com/mambalabs](https://apify.com/mambalabs)
83
+
84
+ ## License
85
+
86
+ MIT
package/SECURITY.md ADDED
@@ -0,0 +1,31 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ The latest published `1.x` release of
6
+ `@mambalabsdev/mcp-company-social-presence-mapper` receives security updates.
7
+
8
+ | Version | Supported |
9
+ | ------- | ------------------ |
10
+ | 1.x | :white_check_mark: |
11
+ | < 1.0 | :x: |
12
+
13
+ ## Reporting a vulnerability
14
+
15
+ Please do not open a public GitHub issue for security reports.
16
+
17
+ Email security reports to **labs@mambamindsetgrowth.com** with:
18
+
19
+ - a description of the issue and its impact,
20
+ - steps to reproduce, and
21
+ - any relevant logs or proof of concept.
22
+
23
+ We aim to acknowledge reports within 3 business days and to provide a remediation
24
+ timeline after triage. Coordinated disclosure is appreciated.
25
+
26
+ ## Handling of credentials
27
+
28
+ This server reads an `APIFY_TOKEN` from the environment and uses it only to call
29
+ the Apify API on your behalf. The token is never logged or written to disk by this
30
+ package. Treat your `APIFY_TOKEN` as a secret and rotate it at
31
+ https://console.apify.com/account/integrations if you believe it has been exposed.
package/build/index.js ADDED
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, join } from "node:path";
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import { z } from "zod";
8
+ const here = dirname(fileURLToPath(import.meta.url));
9
+ const pkg = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
10
+ // Distinctive UA so Apify run meta.userAgent marks MCP-originated runs.
11
+ const USER_AGENT = `mambalabs-mcp ${pkg.name}@${pkg.version}`;
12
+ const APIFY_TOKEN = process.env.APIFY_TOKEN;
13
+ // Drop undefined values so optional inputs are not sent to the actor at all.
14
+ function compact(obj) {
15
+ const out = {};
16
+ for (const [k, v] of Object.entries(obj)) {
17
+ if (v !== undefined)
18
+ out[k] = v;
19
+ }
20
+ return out;
21
+ }
22
+ // The actor types its switches as strings ("true"/"false") for Clay
23
+ // compatibility, because Clay sends every input as a string and a boolean typed
24
+ // field silently receives "false" and reads it as truthy. The model gets a real
25
+ // boolean and the actor gets the string it validates.
26
+ function boolToString(v) {
27
+ return v === undefined ? undefined : v ? "true" : "false";
28
+ }
29
+ // actorPath is the actor's IMMUTABLE Apify actor id, not its slug, so a Store
30
+ // rename never breaks these calls.
31
+ async function runActor(actorPath, actorLabel, input) {
32
+ if (!APIFY_TOKEN) {
33
+ return { isError: true, content: [{ type: "text", text: "APIFY_TOKEN is not set. Create a token at https://console.apify.com/account/integrations and set it as the APIFY_TOKEN environment variable." }] };
34
+ }
35
+ // memory=512 is deliberate and matches the actor's declared
36
+ // defaultRunOptions.memoryMbytes. run-sync-get-dataset-items runs at 2048 MB
37
+ // unless told otherwise, and `apify-actor-start` bills once per GB with a
38
+ // minimum of one, so leaving the default in place would charge the caller
39
+ // more start events per run than the actor asks for. Keep this in step with
40
+ // the actor's defaultRunOptions.
41
+ const url = `https://api.apify.com/v2/acts/${actorPath}/run-sync-get-dataset-items?timeout=300&memory=512`;
42
+ let response;
43
+ try {
44
+ response = await fetch(url, {
45
+ method: "POST",
46
+ headers: {
47
+ Authorization: `Bearer ${APIFY_TOKEN}`,
48
+ "Content-Type": "application/json",
49
+ "User-Agent": USER_AGENT,
50
+ },
51
+ body: JSON.stringify(input),
52
+ });
53
+ }
54
+ catch (err) {
55
+ const message = err instanceof Error ? err.message : String(err);
56
+ return { isError: true, content: [{ type: "text", text: `Could not reach the Apify API: ${message}` }] };
57
+ }
58
+ if (!response.ok) {
59
+ let detail = "";
60
+ try {
61
+ const body = (await response.json());
62
+ if (body?.error?.message)
63
+ detail = ` ${body.error.message}`;
64
+ }
65
+ catch {
66
+ detail = "";
67
+ }
68
+ let message;
69
+ switch (response.status) {
70
+ case 401:
71
+ message = "Invalid Apify token. Check your APIFY_TOKEN environment variable.";
72
+ break;
73
+ case 402:
74
+ message = "Insufficient Apify credits. Check your account balance at https://console.apify.com/billing";
75
+ break;
76
+ case 408:
77
+ message = `The ${actorLabel} run timed out after 300 seconds. Try again, or run the actor on Apify directly for longer jobs.`;
78
+ break;
79
+ default:
80
+ message = `Apify request to ${actorLabel} failed with status ${response.status}.${detail}`;
81
+ }
82
+ return { isError: true, content: [{ type: "text", text: message }] };
83
+ }
84
+ // A 2xx normally carries the dataset array. Pass actor output through
85
+ // unchanged: the wrapper must never reinterpret a status field, because
86
+ // not_extractable, blocked and not_found are different answers and collapsing
87
+ // them is exactly the defect the actor was built to avoid.
88
+ const items = await response.json();
89
+ return { content: [{ type: "text", text: JSON.stringify(items, null, 2) }] };
90
+ }
91
+ const server = new McpServer({
92
+ name: "mamba-meta-brand-presence-mapper",
93
+ version: pkg.version,
94
+ });
95
+ // Instagram Threads Facebook Brand Presence Mapper (immutable actor ID HXh65I0JBjPsunbY8)
96
+ server.registerTool("map_meta_brand_presence", {
97
+ title: "Map Instagram Threads and Facebook Presence",
98
+ description: "Resolve a company domain to its Instagram, Threads and Facebook accounts with follower and post counts, as one flat Clay ready row. The Threads handle is derived from the resolved Instagram handle at no extra discovery cost. Instagram and Threads counts are rounded by Meta and the row carries both the rounded integer and the platform's own display string. Facebook is best effort: Meta serves a login wall to anonymous clients, so blocked is a normal answer there and never a zero. Read only; requires an APIFY_TOKEN and consumes Apify credits per call.",
99
+ annotations: {
100
+ title: "Map Instagram Threads and Facebook Presence",
101
+ readOnlyHint: true,
102
+ destructiveHint: false,
103
+ idempotentHint: true,
104
+ openWorldHint: true,
105
+ },
106
+ inputSchema: {
107
+ company_domain: z.string()
108
+ .optional()
109
+ .describe("Bare company domain, for example shopify.com. Supply this or a handle. With a domain the actor runs full discovery; with a handle it skips straight to the fetch."),
110
+ company_name: z.string()
111
+ .optional()
112
+ .describe("Optional. Improves search accuracy and is what the identity gate checks a discovered profile against, so supplying it reduces wrong matches."),
113
+ handle: z.string()
114
+ .optional()
115
+ .describe("Optional. The Instagram handle with or without the leading @. Supplying it skips Instagram discovery AND gives Threads its handle for free, because Threads handles are Instagram handles (5 of 5 measured)."),
116
+ platforms: z.array(z.enum(["instagram", "threads", "facebook"]))
117
+ .optional()
118
+ .describe("Which of the three Meta surfaces to map. Default is all three. Dropping Facebook is the common choice: it is the least reliable of the three and it costs a fetch to find that out."),
119
+ includeFollowerCounts: z.boolean()
120
+ .optional()
121
+ .describe("When \"true\" (default) the profile page is fetched and the counts are extracted. Set \"false\" to resolve the profile URL only, which is cheaper and needs no proxy. Sent as a string for Clay compatibility."),
122
+ skipCache: z.boolean()
123
+ .optional()
124
+ .describe("When \"false\" (default) a successful lookup is cached for seven days and reused. Set \"true\" to force a fresh fetch. Sent as a string for Clay compatibility."),
125
+ },
126
+ }, async ({ company_domain, company_name, handle, platforms, includeFollowerCounts, skipCache }) => {
127
+ return runActor("HXh65I0JBjPsunbY8", "Instagram Threads Facebook Brand Presence Mapper", compact({
128
+ company_domain,
129
+ company_name,
130
+ handle,
131
+ platforms,
132
+ includeFollowerCounts: boolToString(includeFollowerCounts),
133
+ skipCache: boolToString(skipCache),
134
+ }));
135
+ });
136
+ const transport = new StdioServerTransport();
137
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@mambalabsdev/mcp-meta-brand-presence-mapper",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for the Mamba Labs Instagram Threads Facebook Brand Presence Mapper actor on Apify. Resolve a company domain to its Instagram, Threads and Facebook accounts with follower and post counts.",
5
+ "type": "module",
6
+ "bin": {
7
+ "mcp-meta-brand-presence-mapper": "./build/index.js"
8
+ },
9
+ "files": [
10
+ "build",
11
+ "README.md",
12
+ "LICENSE",
13
+ "SECURITY.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc && chmod +x build/index.js",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "model-context-protocol",
22
+ "instagram",
23
+ "threads",
24
+ "facebook",
25
+ "social-media",
26
+ "follower-counts",
27
+ "apify",
28
+ "clay"
29
+ ],
30
+ "author": "Mamba Labs <labs@mambamindsetgrowth.com>",
31
+ "license": "MIT",
32
+ "homepage": "https://apify.com/mambalabs",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/mambalabsdev/mcp-meta-brand-presence-mapper"
36
+ },
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.12.0",
42
+ "zod": "^3.23.8"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^20.14.0",
46
+ "typescript": "^5.6.0"
47
+ },
48
+ "mcpName": "com.mambabuilt/mcp-meta-brand-presence-mapper"
49
+ }