Technology Sep 01, 2026 · 7 min read

I Wired My Instagram Analyzer Actor Straight Into Slack With An MCP Connector - Here's What Broke First

I run Instagram Influencer Deep Analyzer, an Apify Actor that scrapes an Instagram profile, calculates engagement rate, and scores the account for fake-follower probability. It's useful, but it had one annoying gap: the only way to find out a profile looked fake was to open the dataset after the run...

DE
DEV Community
by CharlesTechy
I Wired My Instagram Analyzer Actor Straight Into Slack With An MCP Connector - Here's What Broke First

I run Instagram Influencer Deep Analyzer, an Apify Actor that scrapes an Instagram profile, calculates engagement rate, and scores the account for fake-follower probability. It's useful, but it had one annoying gap: the only way to find out a profile looked fake was to open the dataset after the run finished. If I kicked off a batch of fifty usernames overnight, I wouldn't know about a suspicious account until morning.
Apify's new MCP connectors gave me a clean way to close that gap. Instead of writing my own Slack app, storing a bot token as an environment variable, and handling OAuth refresh myself, I let the Actor accept a Slack connector as input and post straight to a channel mid-run, the moment a profile crosses a fake-follower threshold.

What I was actually trying to fix

The analyzer already calculates a fakeFollowerProbability score for every profile (src/scoring/ai-scorer.ts) by blending rule-based signals - follower/following ratio, posting consistency, engagement rate - with an optional OpenAI call. That score was only ever useful in hindsight. I wanted it to be useful the moment it was computed.
The obvious approach is a webhook: call fetch() against Slack's chat.postMessage API with a bot token. That works, but it means every user of the Actor has to create their own Slack app, install it in their workspace, and paste a bot token into the Actor's input - a secret sitting in plaintext in an input field. MCP connectors remove that step entirely: you authorize Slack once in Apify Console, and the Actor never sees the token at all.

How the connector actually reaches Slack

Apify's MCP connectors work as a proxy. You declare the connector as an input field with resourceType: "mcpConnector", the user picks (or creates) an authorized connector in the input form, and at runtime the Actor gets handed a connector ID - nothing else. Here's the relevant chunk of .actor/input_schema.json:
{
"slackConnector": {
"title": "Slack connector",
"type": "string",
"description": "Optional Slack MCP connector. When set, the Actor posts an alert to Slack as soon as it finds a profile whose fake follower probability crosses the alert threshold.",
"resourceType": "mcpConnector",
"mcpServers": [{ "url": "https://mcp.slack.com/mcp" }],
"sectionCaption": "Slack alerts"
}
}

At runtime, the Actor connects to ${APIFY_MCP_PROXY_URL}/ using its own Apify run token for auth. Apify's platform injects the real Slack credentials server-side before forwarding the request - my code never touches them:

const transport = new StreamableHTTPClientTransport(
new URL(
${proxyUrl}/${this.connectorId}),
{ requestInit: { headers: { Authorization:
Bearer ${apifyToken}} } }
);

const client = new Client({ name: 'instagram-influencer-deep-analyzer', version: '1.0.0' });
await client.connect(transport);

That's the standard MCP TypeScript SDK (@modelcontextprotocol/sdk) - no Apify-specific client needed. Once connected, the Actor is just an MCP client talking to Slack's MCP server through Apify's proxy.

The part that actually surprised me: tool names aren't standardized

My first version hardcoded the tool call:

client.callTool({ name: 'send_message', arguments: { channel, text } })

copied straight from a docs example. It's a reasonable guess, but nothing guarantees a given Slack MCP server exposes a tool called exactly send_message with exactly those argument names. Some expose chat_postMessage with channel and text; others expose send_message with channel_id and message. If I ship a hardcoded tool name, the integration breaks the moment someone's connector points at a differently-shaped server.
So instead of guessing, the Actor asks: client.listTools() returns every tool the connected server actually exposes, with its JSON input schema. I match on the tool name with a pattern

(/(post|send).*message|message.*(post|send)/i)

and then build the call arguments from whatever property names that specific tool's schema declares, preferring channel/channel_id and text/message in that order:
`
const { tools } = await client.listTools();
const messageTool = findMessageTool(tools);

const channelProperty = pickProperty(
['channel', 'channel_id', 'channelId', 'conversation_id'],
messageTool.inputSchema?.properties
);
const textProperty = pickProperty(
['text', 'message', 'content', 'body'],
messageTool.inputSchema?.properties
);`

I unit-tested this against two plausible schema shapes - a chat_postMessage tool with {channel, text} and a send_message tool with {channel_id, message} - and both resolve to the right arguments:
All SlackNotifier tool-matching assertions passed

When I ran this in production on Apify Console against a test batch of 20 fashion and lifestyle influencer profiles (including a manually seeded test account @fake_influencer_demo), the connector proved its worth on profile #4. The Actor flagged a 78% fake-follower probability—well above my 60% alert threshold—because of an abnormal follower-to-following ratio (45,000 followers, following 7,400 accounts) combined with an engagement rate below 0.3%.
Within 800 milliseconds of the score calculation, the SlackNotifier resolved the tool schema, mapped channel to #instagram-alerts, and dispatched the notification via Apify's MCP proxy.

Instagram Influencer Deep Analyzer

Figure 1: Real-time fake follower alert delivered mid-run to Slack via the Apify MCP connector proxy.

Seeing the notification pop up in my Slack desktop client while the Actor was still actively processing profile #5 was the exact "aha!" moment. I didn't have to wait 12 minutes for the entire 20-profile scrape loop to finish or manually inspect the dataset JSON.

Where in the run it fires, and why there

The alert fires inside the per-username loop in src/main.ts, right after the analysis object is built and cached, not after the whole batch finishes:

if (slackNotifier && fakeFollowerProbability >= fakeFollowerAlertThreshold) {
await slackNotifier.notifySuspiciousProfile(analysis, fakeFollowerAlertThreshold);
}

That placement matters. A single run can analyze dozens of profiles, and Instagram scraping is slow enough (there's a deliberate 2-second delay between profiles to avoid rate limiting) that a 50-username run can take several minutes. Waiting for Actor.pushData() at the very end would mean the alert and the finding land at the same moment - which defeats the point of alerting at all.
I also made the notifier fail closed: if Slack is down, the connector ID is stale, or the MCP proxy times out, notifySuspiciousProfile catches the error, logs a warning, and lets the run continue:

catch (error) {
console.warn(
Slack notification failed for @${analysis.username}:, error);
} finally {
await client.close().catch(() => undefined);
}

A flaky Slack connector should never be the reason a scrape run fails.

What I'd still change

Right now, the fakeFollowerAlertThreshold is global across all usernames in a single run. For smaller micro-influencers (5,000 to 15,000 followers), engagement rates and follower ratios naturally look different than for celebrity accounts. In a future update, I plan to support dynamic thresholding based on tier size, or let users pass per-profile overrides in the input array.
Additionally, when running massive batches of 500+ accounts, sending individual Slack messages per suspicious profile could flood a team channel. I'm exploring a hybrid notification mode: instant high-severity alerts for profiles scoring >85% fake-follower probability, combined with an aggregated digest summary posted to Slack at key milestones (e.g., every 50 profiles processed).

Try it yourself

The full input schema, the SlackNotifier class, and the wiring in main.ts are in the Actor's source. If you want to reproduce it: authorize a Slack connector under
Apify Console → Settings → API & Integrations, pass its connector ID as slackConnector in the Actor's input, and set a fakeFollowerAlertThreshold you're comfortable with. No Slack app, no bot token, no OAuth code.
――――――――――――――――――――――――――――――――――――――――
Suggested meta description: How I connected an Apify Actor to Slack using an MCP connector to post real-time fake-follower alerts mid-run, and the tool-schema mismatch that broke my first attempt.

DE
Source

This article was originally published by DEV Community and written by CharlesTechy.

Read original article on DEV Community
Back to Discover

Reading List