People do not always announce buying intent on a landing page. They ask for recommendations in a subreddit, complain about a workflow, or look for an alternative to a tool they already use.
I saw that pattern while building and testing my Reddit Scraper Actor. I kept repeating the same keywords and communities across runs. These were no longer one-off research jobs; they were monitors. The missing piece was delivery. A useful post could sit in an Apify dataset until someone remembered to check it.
I added optional Model Context Protocol (MCP) delivery so the Actor can send completed results to another service. This Reddit lead generation workflow uses Slack's official MCP server. For this guide, I configured the server, discovered its live tool schema, ran a one-result test, and verified the message in a private Slack channel. The first attempt failed for a non-obvious Slack setting, which is included below because it is exactly the kind of integration detail that polished examples tend to hide.
What we are building
Apify schedule -> Reddit Scraper -> structured Reddit posts and comments
-> Apify MCP connector -> Slack MCP server -> private lead-alert channel -> human review
The Actor searches recent Reddit discussions, stores the complete results in its dataset, and sends selected posts through an Apify MCP connector. A Slack alert can contain the post title, subreddit, score, comment count, link, and selected comments.
A person still decides whether to respond. This workflow surfaces relevant public conversations; it does not automate unsolicited Reddit replies.
Two Apify features have similar names but opposite directions:
- An Apify MCP connector lets an Actor call an external MCP server. That is what sends the alerts in this tutorial.
- The Apify MCP server lets an external AI client call Apify Actors and tools. It is useful for Claude, ChatGPT, or Cursor, but it is not the delivery path here.
Prerequisites
You will need:
- an Apify account;
- Reddit Scraper;
- permission to create a Slack app and a private destination channel;
- permission to create Apify tasks and schedules; and
- a small list of brand, competitor, and problem-related searches.
Slack exposes its official Streamable HTTP MCP endpoint at https://mcp.slack.com/mcp. It requires a registered internal or Marketplace Slack app with a confidential OAuth client. Slack does not support Dynamic Client Registration for this endpoint, so the one-click OAuth option is not enough; use Apify's Own OAuth client flow. Some Console versions label the same option Your own OAuth client.
1. Start with high-intent Reddit searches
A broad category keyword creates noise. A phrase describing a decision or painful workflow creates better alerts.
| Broad query | Higher-intent query |
|---|---|
| scheduling software | alternative to calendly |
| appointment tools | keep missing client calls |
| booking app | double booked |
| CRM | looking for a simple CRM |
A useful starter set contains your brand and common misspellings, two or three competitor names, a few problem phrases, and buying-intent language such as looking for, alternative to, or would pay for.
Reddit Scraper has four controls that matter for monitoring:
-
searchSort: "new"orders results chronologically; -
searchTime: "day"limits the search window; -
withinCommunitylimits a run to one subreddit; and -
maxPostsCountcaps the posts collected.
Sorting by new does not remove old results, so use a time window as well.
Figure 1: Search for a problem or buying-intent phrase, sort by New, and use a recent time window.
Start with 10 to 20 posts. It is easier to improve five weak alerts than to clean up a Slack channel after sending hundreds.
2. Configure Slack for Reddit lead generation alerts
Create the private destination first: in Slack, select Add channels -> Create a new channel, give it a clear test name, and make it private. The Slack user who authorizes the app must be a member of that channel and able to post there.
Next open Slack app management, select Create New App -> From scratch, name the app, and choose the receiving workspace. In the new app, open OAuth & Permissions and add this redirect URL:
https://console.apify.com/oauth/mcp-proxy
Add chat:write as a User Token Scope. Then open the app's Agents page and enable Slack Model Context Protocol (MCP) Server. This toggle is separate from the app's OAuth scopes and is easy to miss. Open Basic Information -> App Credentials to find the client ID and client secret required by Apify.
In Apify Console, open Settings -> API & Integrations -> MCP connectors, select Add connector, and enter:
Name: Slack lead alerts
Server URL: https://mcp.slack.com/mcp
Authentication: Own OAuth client (or Your own OAuth client)
Authorization URL: https://slack.com/oauth/v2_user/authorize
Token URL: https://slack.com/api/oauth.v2.user.access
Supply the Slack app's client ID and client secret in Apify's secure fields. They should never appear in Actor input, datasets, source code, screenshots, or logs.
Review the Slack consent page instead of approving it mechanically. The workflow needs chat:write, not workspace-wide search, file, user, and channel-management permissions. In my August 2026 test, Apify generated the authorization popup with Slack's much broader advertised scope set. This was observed Apify behavior, not a Slack-supported scope picker. I completed the least-privilege flow as follows:
- In the same OAuth popup, focus the address bar without closing or duplicating the window.
- Change only the
scopequery parameter tochat:writeand remove the emptyuser_scopeparameter. - Preserve
client_id,redirect_uri,state,code_challenge,code_challenge_method, andresponse_typeexactly as Apify generated them. - Reload that edited URL in the same popup and verify that the consent screen requests only permission to send messages as the authorizing user.
- Approve the request and return to Apify, where the connector should show the discovered tools.
That URL edit is a workaround for the current connector flow. Do not alter, share, or reuse the OAuth state or Proof Key for Code Exchange (PKCE) values. If your administrator does not approve this workaround, stop rather than granting the broader scopes and use an administrator-managed least-privilege integration instead. Re-check Apify's connector UI before following this step because native scope selection may replace the workaround.
After successful authorization, the live server exposed three tools with this scope:
-
slack_send_message; -
slack_schedule_message; and -
slack_send_message_draft.
The slack_send_message input schema required exactly two values:
{
"channel_id": "string",
"message": "string"
}
Optional fields included thread_ts, reply_broadcast, draft_id, and unfurl_app_links. I disabled Allow all in the connector and permitted only slack_send_message.
Figure 2: OAuth scope and connector tool allowlists are separate controls. This connector can call only the immediate-send operation.
Apify stores the Slack credential server-side. The Actor receives a connector ID and authenticates to Apify's MCP proxy with its run token; it never receives the Slack OAuth token.
3. Configure Reddit Scraper delivery
Open Reddit Scraper and expand Send to your apps (MCP integration, optional).
Copy the Slack channel ID before filling mcpTarget: open the destination channel, select its name to open channel details, and copy the Channel ID shown on the About tab. You can also copy the channel link and use the segment beginning with C. Do not use the human-readable #channel-name in mcpTarget.
Figure 3: The final configuration uses New, Last day, the authorized Slack connector, and one message per post.
| Field | What it controls |
|---|---|
mcpConnector |
Managed connector selected in Apify Console |
mcpMode |
One message per post (perPost) or one digest (summary) |
mcpTarget |
Destination expected by the tool; for Slack, a channel ID |
mcpComments |
Ignore comments, bundle them, or send separate follow-ups |
mcpCommentsPerPost |
Highest-scoring comments included per post |
mcpMessage |
Deterministic {{field}} template |
mcpTool |
Exact tool-name override |
mcpArguments |
Extra rendered arguments merged over inferred values |
mcpMaxItems |
Maximum posts dispatched in perPost mode |
For Slack lead alerts, use perPost. Each opportunity becomes a message that can be discussed or assigned independently.
A useful template is:
**{{title}}**
{{communityName}} | score {{score}} | {{commentsCount}} comments
{{postUrl}}
Templates are deterministic substitutions, not AI prompts. Missing values render as empty strings, and nested dataset paths are supported.
Figure 4: Control formatting, comment delivery, the exact MCP tool, and the dispatch cap.
Here is the schema-validated smoke-test input used for the final screenshots. Replace the two placeholders with values from your account:
{
"searchTerms": ["AI"],
"searchPosts": true,
"searchComments": false,
"searchCommunities": false,
"searchSort": "new",
"searchTime": "day",
"crawlCommentsPerPost": false,
"maxPostsCount": 1,
"mcpConnector": "REPLACE_WITH_APIFY_CONNECTOR_ID",
"mcpMode": "perPost",
"mcpTarget": "REPLACE_WITH_SLACK_CHANNEL_ID",
"mcpComments": "ignore",
"mcpTool": "slack_send_message",
"mcpMessage": "**{{title}}**\n{{communityName}} | score {{score}} | {{commentsCount}} comments\n{{postUrl}}",
"mcpMaxItems": 1
}
The broad, harmless AI query makes a one-result transport test more reliable than a narrow lead phrase. If it returns no post for day, widen searchTime to week and rerun before changing any connector settings. Once delivery works, replace AI with the high-intent phrases from step 1, optionally set withinCommunity, and raise both caps gradually.
The live Slack schema uses channel_id and message. Reddit Scraper maps mcpTarget to the first destination-like string field and maps the rendered template to the first message-like string field, so no provider-specific request body is required.
4. Test the Reddit lead generation workflow
Run the Actor manually before creating a task or schedule. Use maxPostsCount: 1 and mcpMaxItems: 1 for the first end-to-end test.
My first run scraped one post but recorded mcp_failed. The proxy surfaced an upstream HTTP 400 as a Server-Sent Events (SSE) connection error. That message initially suggested a transport incompatibility, because Slack explicitly supports Streamable HTTP and not SSE.
A direct authenticated initialize request to Slack's MCP endpoint returned the actual cause: the Slack app was not enabled for Slack MCP server access. After I enabled Agents -> Slack Model Context Protocol (MCP) Server, I recreated and reauthorized the Apify connector so it could rediscover tools. No Actor transport or argument-mapping change was needed.
The second run discovered the three Slack tools, called slack_send_message, and finished successfully. After restricting the connector to that one allowed tool, I repeated the one-item run; it dispatched one call and produced a second message in the private channel.
That success exposed one small code issue. The existing default used Slack's legacy single-asterisk formatting, but the official MCP tool accepts standard Markdown, so the title rendered in italics. I changed the Actor's default and schema prefill from *{{title}}* to **{{title}}**, reran the test, and confirmed that both the alert heading and post title rendered bold.
Figure 5: The test extracted one post, dispatched one MCP call through slack_send_message, and preserved a successful crawl result.
The private destination channel then contained the rendered Reddit alert and source link:
Figure 6: The external result confirms that connector authorization, schema mapping, and the Slack write all worked.
Check a manual run in this order:
- Verify the expected Reddit rows in the dataset.
- Confirm deliverable rows have
dataType: "post". - Inspect the log for the selected MCP tool and dispatched-call count.
- Open Slack and verify the channel, formatting, and links.
- Compare the number of delivered posts with Slack messages.
- Tighten searches before raising the caps.
For diagnostics, the implementation supports MCP_DEBUG_LIST_TOOLS. It logs tool names and schemas and exits without sending messages. An invalid explicit mcpTool does not fall back to automatic selection.
5. Turn the working input into a monitor
Once the manual run is clean:
- Return to the Actor's Input tab, open Actions, and choose Save as a new task.
- Name the task, save it, and open Schedules -> Create schedule in Apify Console.
- Choose the saved task as the action, set an hourly or two-hour interval, and save the schedule.
- Use Run now once and confirm the new run, dataset row, dispatch log, and Slack message before leaving the schedule enabled.
- Review alert quality for several days and remove noisy searches before increasing volume.
Reddit Scraper does not currently keep a cross-run set of posts delivered through MCP. Overlapping search windows can therefore repeat a post. Reduce duplicates with a shorter searchTime, fewer overlapping queries, a slower schedule, or downstream deduplication on the Reddit post id.
How the Actor maps Reddit data to Slack's MCP schema
In the Actor source, the Reddit-specific code builds an ordered message list after the crawl:
// Excerpt: products/reddit/src/utils/mcp.ts
const posts = allItems.filter((it) => it?.dataType === "post");
const maxItems = Math.max(1, input.mcpMaxItems ?? DEFAULT_MAX_ITEMS);
for (const post of posts.slice(0, maxItems)) {
let text = renderTemplate(template, post);
if (commentMode === "bundle" && top.length) {
text += `\n\n${renderCommentDigest(top)}`;
}
messages.push({ message: text, fields: postFields(post), item: post });
}
The shared dispatcher then connects to Apify's managed proxy, discovers tools, reads the chosen tool's schema, and sends serially:
// Excerpt: packages/core/src/mcp.ts
const { tools } = await client.listTools();
const toolName = selectWriteTool(tools as McpTool[], input.mcpTool);
const toolSchema = (tools as McpTool[])
.find((tool) => tool.name === toolName)?.inputSchema;
const target = input.mcpTarget?.trim() || undefined;
const overrideArgs = (input.mcpArguments ?? {}) as Record<string, unknown>;
for (const { message, fields, item } of messages) {
await client.callTool({
name: toolName,
arguments: {
...buildToolArgs(toolSchema, message, fields, target),
...(interpolateDeep(overrideArgs, item) as Record<string, unknown>),
},
});
}
For the observed Slack tool, schema inspection maps the alert to message and the destination to channel_id. The same dispatcher can adapt to other MCP servers whose schemas use fields such as text, content, rows, or properties.
Reddit lead generation failure modes
Scraping finishes before MCP delivery. A connector failure normally leaves the collected dataset intact, but partial delivery is possible.
| Failure | Observed or expected behavior |
|---|---|
| Slack MCP Server toggle is disabled | Upstream initialization returns HTTP 400; enable it, then reauthorize |
| Managed proxy variables are unavailable locally | Delivery records mcp_unavailable
|
| Dataset read fails | Delivery records dataset_read_failed
|
| Explicit tool is missing | Delivery records no_write_tool
|
| No write-like tool is exposed | Delivery records no_write_tool
|
| Connection, discovery, or tool call throws | Delivery records dispatch_failed
|
| Connector is empty | MCP delivery is a silent no-op |
| No post messages are built | The Actor does not connect to the server |
There is no retry loop around individual tool calls. Calls are serial, and one failed call stops the remaining loop. Earlier Slack messages may already exist even when the run records dispatch_failed; check Slack before rerunning to avoid duplicates.
mcpMaxItems caps posts, not total calls. In separate-comment mode, one post can generate one post message plus several comment messages.
Security, Reddit policies, and human review
- Give the Slack app only the scopes required by the permitted MCP tool.
- Disable Allow all and allow only
slack_send_messagefor this connector. - Send test alerts to a designated private channel.
- Keep
mcpMaxItemslow until the query quality is proven. - Prefer a managed connector over placing a bearer token in Actor input.
- Never put OAuth credentials in datasets, templates, screenshots, or logs.
- Treat public Reddit content as context, not consent for automated outreach.
- Keep a person responsible for deciding whether and how to respond.
Use this workflow in accordance with Reddit's User Agreement, Public Content Policy, privacy requirements, and applicable law. Do not use it to profile sensitive traits, harass users, or automate spam.
Slack also warns about mixing data across several MCP servers. Review every endpoint that can receive workspace context, and avoid sending workspace data through an MCP service you do not trust.
After a temporary proof-of-concept, pause or delete the Apify schedule and task, remove the MCP connector from Apify, revoke the Slack app's OAuth grant, and archive the test channel. Delete the Slack app only if no other connector or workflow uses it.
What I would improve next
The useful first version is working: scheduled discovery, deterministic formatting, live tool discovery, least-privilege connector controls, comments, and safety caps.
The next improvements are persistent delivered-post IDs, retry policies for transient MCP errors, explicit partial-success counts, and destination-specific templates. The main lesson, however, is that query quality matters more than scrape volume. Start with three phrases indicating a real problem or decision, send a tiny sample to Slack, review it with a human, and improve the searches before scheduling more runs.
That turns Reddit Scraper from a dataset generator into a monitor a team can actually use.





Top comments (0)