@mambalabsdev/mcp-pinterest-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,85 @@
1
+ # Pinterest Brand Presence Mapper MCP Server
2
+
3
+ [![Smithery](https://smithery.ai/badge/mambabuilt/mcp-pinterest-brand-presence-mapper)](https://smithery.ai/servers/mambabuilt/mcp-pinterest-brand-presence-mapper) [![Glama score](https://glama.ai/mcp/servers/mambalabsdev/mcp-pinterest-brand-presence-mapper/badges/score.svg)](https://glama.ai/mcp/servers/mambalabsdev/mcp-pinterest-brand-presence-mapper) [![npm version](https://img.shields.io/npm/v/@mambalabsdev/mcp-pinterest-brand-presence-mapper)](https://www.npmjs.com/package/@mambalabsdev/mcp-pinterest-brand-presence-mapper) [![npm downloads](https://img.shields.io/npm/dm/@mambalabsdev/mcp-pinterest-brand-presence-mapper)](https://www.npmjs.com/package/@mambalabsdev/mcp-pinterest-brand-presence-mapper) [![license](https://img.shields.io/github/license/mambalabsdev/mcp-pinterest-brand-presence-mapper)](https://github.com/mambalabsdev/mcp-pinterest-brand-presence-mapper/blob/main/LICENSE)
4
+
5
+ MCP server for the Mamba Labs **Pinterest Brand Presence Mapper** actor on Apify.
6
+
7
+ Resolve a company domain to its Pinterest business account with exact follower, pin and board counts.
8
+
9
+ ## What it does
10
+
11
+ Resolve a company domain to its Pinterest business account and return EXACT follower, following, pin and board counts, plus the claimed website, verified merchant status and last pin date, as one flat Clay ready row. Pinterest serves real integers, so these counts can be summed across a list. Board count is a better activity signal than followers for a consumer brand, because boards are curation effort. Pinterest handles are rarely the domain stem, so a guessed account that fails the identity check is reported as identity_mismatch with no counts rather than returning a stranger's numbers. 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-pinterest-brand-presence-mapper": {
21
+ "command": "npx",
22
+ "args": ["-y", "@mambalabsdev/mcp-pinterest-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/pinterest-brand-presence-mapper).
36
+
37
+ ## Example prompts
38
+
39
+ - "How many Pinterest followers and boards does brooklinen.com have?"
40
+ - "Is chewy.com a verified merchant on Pinterest?"
41
+ - "Compare Pinterest board counts across patagonia.com and article.com."
42
+
43
+ ## Tool and inputs
44
+
45
+ Tool: `map_pinterest_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 Pinterest username from pinterest.com/<handle>. Supplying it skips discovery and, more importantly, skips the identity risk: Pinterest h |
52
+ | `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 |
53
+ | `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 |
54
+
55
+ ## Reading the output
56
+
57
+ Every row carries a per platform `_status` field, and it is the field to read
58
+ first. The vocabulary is the same across the whole Mamba Labs social family:
59
+
60
+ | Status | Meaning |
61
+ |---|---|
62
+ | `ok` | fetched and parsed, the value is there |
63
+ | `not_found` | we looked and there is no such profile |
64
+ | `not_extractable` | the profile exists and the value is not on the wire to us |
65
+ | `blocked` | the platform refused us, worth retrying later |
66
+ | `identity_mismatch` | we found a real profile and it belongs to someone else |
67
+ | `skipped` | you did not ask for this platform |
68
+
69
+ **`false` and `null` are never interchangeable.** `false` means we looked and the
70
+ answer is no. `null` means we could not look. If you filter for companies with no
71
+ presence, filter on `false`, because `null` rows are unknown rather than absent.
72
+
73
+ ## Full actor documentation
74
+
75
+ [apify.com/mambalabs/pinterest-brand-presence-mapper](https://apify.com/mambalabs/pinterest-brand-presence-mapper)
76
+
77
+ ## Mamba Labs GTM Suite
78
+
79
+ Mamba Labs builds a fleet of GTM enrichment actors that share one flat, Clay
80
+ ready output convention, so their rows join on `company_domain` with no cleaning
81
+ step. Full fleet: [apify.com/mambalabs](https://apify.com/mambalabs)
82
+
83
+ ## License
84
+
85
+ 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,133 @@
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=1024 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=1024`;
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-pinterest-brand-presence-mapper",
93
+ version: pkg.version,
94
+ });
95
+ // Pinterest Brand Presence Mapper (immutable actor ID d1HvjZfKhgvWsoEwn)
96
+ server.registerTool("map_pinterest_brand_presence", {
97
+ title: "Map Pinterest Brand Presence",
98
+ description: "Resolve a company domain to its Pinterest business account and return EXACT follower, following, pin and board counts, plus the claimed website, verified merchant status and last pin date, as one flat Clay ready row. Pinterest serves real integers, so these counts can be summed across a list. Board count is a better activity signal than followers for a consumer brand, because boards are curation effort. Pinterest handles are rarely the domain stem, so a guessed account that fails the identity check is reported as identity_mismatch with no counts rather than returning a stranger's numbers. Read only; requires an APIFY_TOKEN and consumes Apify credits per call.",
99
+ annotations: {
100
+ title: "Map Pinterest Brand 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 Pinterest username from pinterest.com/<handle>. Supplying it skips discovery and, more importantly, skips the identity risk: Pinterest handles are rarely the domain stem."),
116
+ includeFollowerCounts: z.boolean()
117
+ .optional()
118
+ .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."),
119
+ skipCache: z.boolean()
120
+ .optional()
121
+ .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."),
122
+ },
123
+ }, async ({ company_domain, company_name, handle, includeFollowerCounts, skipCache }) => {
124
+ return runActor("d1HvjZfKhgvWsoEwn", "Pinterest Brand Presence Mapper", compact({
125
+ company_domain,
126
+ company_name,
127
+ handle,
128
+ includeFollowerCounts: boolToString(includeFollowerCounts),
129
+ skipCache: boolToString(skipCache),
130
+ }));
131
+ });
132
+ const transport = new StdioServerTransport();
133
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@mambalabsdev/mcp-pinterest-brand-presence-mapper",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for the Mamba Labs Pinterest Brand Presence Mapper actor on Apify. Resolve a company domain to its Pinterest business account with exact follower, pin and board counts.",
5
+ "type": "module",
6
+ "bin": {
7
+ "mcp-pinterest-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
+ "pinterest",
23
+ "social-media",
24
+ "follower-counts",
25
+ "ecommerce",
26
+ "apify",
27
+ "clay"
28
+ ],
29
+ "author": "Mamba Labs <labs@mambamindsetgrowth.com>",
30
+ "license": "MIT",
31
+ "homepage": "https://apify.com/mambalabs",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/mambalabsdev/mcp-pinterest-brand-presence-mapper"
35
+ },
36
+ "engines": {
37
+ "node": ">=18"
38
+ },
39
+ "dependencies": {
40
+ "@modelcontextprotocol/sdk": "^1.12.0",
41
+ "zod": "^3.23.8"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^20.14.0",
45
+ "typescript": "^5.6.0"
46
+ },
47
+ "mcpName": "com.mambabuilt/mcp-pinterest-brand-presence-mapper"
48
+ }