DedalusDedalus Labs
Marketplace
Pricing
Blog
Docs
DedalusDedalus Labs

The drop-in MCP gateway that connects any LLM to any MCP server, local or fully-managed on our marketplace. We take care of hosting, scaling, and model hand-offs, so you can ship production-grade agents without touching Docker or YAML.

Product

  • Pricing
  • API
  • Documentation

Company

  • About
  • Blog
  • Use Cases
  • Careers
  • Contact

Legal

  • PrivacyPrivacy Policy
  • TermsTerms of Service

© 2026 Dedalus Labs. All rights reserved.

Command Palette

Search for a command to run...

Use case

How to Build a CRM Agent That Searches X (Twitter) for High-Value Leads and Warm Intros

In this tutorial, build and publish an agent that combines X API and web search to uncover high‑value second‑degree leads in your X network, with DAuth managing X API secrets securely.

Written by
PA
Palash AwasthiHead of Growth
Jan 21, 20265 min
Palash Awasthi·Jan 21, 2026·5 min read

Today we’ll walk through a real CRM agent built on the Dedalus SDK. This agent:

  • Searches X for accounts related to a target profile
  • Uses web search as a fallback when X profiles are missing or incomplete
  • Streams results into a clean, CRM‑ready profile
  • Uses DAuth so your X API key is never exposed to the MCP server or Dedalus

You can use this pattern to:

  • Qualify second‑degree leads by interest, activity, and relationships
  • Map social graphs around a target customer or account

GitHub repo: dedalus-labs/web-crm-agent

Prerequisites: Install and Set Up the Dedalus SDK

Install dependencies

bash
pip install dedalus-labs dedalus-mcp python-dotenv

Before you get started, create a Dedalus API key using the dashboard and set it as an environment variable in your .env file, and add the base URL needed for DAuth.

jsx
DEDALUS_API_KEY=your-api-key
DEDALUS_AS_URL=https://as.dedaluslabs.ai

You’ll also need to obtain an external X API key from X and set your X bearer token in your .env file. Note that the free version of the X API is limited to 500 posts and 100 reads per month.

bash
X_BEARER_TOKEN=your-x-bearer-token

Full Agent Code (Also in GitHub)

Once you’ve set up your Dedalus API key and environment variables, you can run the agent using this code - just replace the name and x_username variables with your target profile:

python
import asyncio
import os
from dedalus_labs import AsyncDedalus, DedalusRunner
from dedalus_labs.utils.stream import stream_async
from dedalus_mcp.auth import Connection, SecretKeys, SecretValues
from dotenv import load_dotenv
load_dotenv()
# The user we are researching
# !! Remember to update this to a real account !!
name = "Jane Doe"
x_username = "@JaneDoe"
# Connection: schema for X/Twitter API
# Note: Some providers may also require an additional "auth_header_format" parameter in Connection(). X does not.
x = Connection(
name="x",
secrets=SecretKeys(token="X_BEARER_TOKEN"),
base_url="https://api.x.com",
)
# SecretValues: binds actual credentials to a Connection schema.
# Encrypted client-side, decrypted in secure enclave at dispatch time.
x_secrets = SecretValues(x, token=os.getenv("X_BEARER_TOKEN", ""))
async def main():
client = AsyncDedalus(timeout=900) # 15 minutes (unit = seconds)
runner = DedalusRunner(client)
response = runner.run(
input=f"""You are an expert CRM Research Agent. Your objective is to identify high-value second-degree connections for {name} ({x_username}).
1. Use x-api-mcp (e.g., x_get_followers, x_get_user_by_username) to go through all of {name}'s followers.
2. Research each follower's own network (second-degree) and their professional background.
3. If X info is sparse, use brave-search-mcp to find more details on the web. Explicitly mention it when you cannot access X.
4. Find at least 15 high-value connections for {name} ({x_username}). These high-value connections should be users that are not already in {name}'s network.
5. For EVERY high-value connection found, format the output in a bulleted summary:
- Name: [Full name]
- X/Social: [Handle/Link]
- Bio: [Brief summary]
- Key Interests: [Topic 1, Topic 2]
- Connection Logic: [Why they are a good lead]""",
model="anthropic/claude-opus-4-5",
mcp_servers=["windsor/x-api-mcp", "tsion/brave-search-mcp"],
credentials=[x_secrets],
stream=True,
)
await stream_async(response)
if __name__ == "__main__":
asyncio.run(main())

Follow the rest of the tutorial to see how we built this agent, step-by-step.

Step 1: Making a Basic Agent with the Dedalus SDK

To create any basic agent on Dedalus, you need to define the following variables:

  • input: instructions and context for the agent. e.g., what problem the agent is trying to solve and how
  • model: which of our AI models your agent will use
  • mcp_servers: which MCP servers your agent will use
python
...
response = runner.run(
input=f"""You are an expert CRM Research Agent. Your objective is to identify high-value second-degree connections for {name} ({x_username}).
1. Use x-api-mcp (e.g., x_get_followers, x_get_user_by_username) to go through all of {name}'s followers.
2. Research each follower's own network (second-degree) and their professional background.
3. If X info is sparse, use brave-search-mcp to find more details on the web. Explicitly mention it when you cannot access X.
4. Find at least 15 high-value connections for {name} ({x_username}). These high-value connections should be users that are not already in {name}'s network.
5. For EVERY high-value connection found, format the output in a bulleted summary:
- Name: [Full name]
- X/Social: [Handle/Link]
- Bio: [Brief summary]
- Key Interests: [Topic 1, Topic 2]
- Connection Logic: [Why they are a good lead]""",
model="anthropic/claude-opus-4-5",
mcp_servers=["windsor/x-api-mcp", "tsion/brave-search-mcp"],
credentials=[x_secrets],
stream=True,
)
...

This CRM agent uses two MCP servers:

  • tsion/brave-search-mcp for web search
  • windsor/x-api-mcp for X (Twitter) profile and tweet search

Using both gives the agent richer context about the accounts it finds on X and lets it fall back to web search if it cannot find good matches directly on X.

You can add as many MCP servers as you need by populating the mcp_servers list. Note: each server fills the agent's context window, so adding too many may cause unexpected behavior.

For context, these are the tools built into the X MCP that your agent can access:

python
@tool(
description="Get an X user by their username",
tags=["user", "read"],
annotations=ToolAnnotations(readOnlyHint=True),
)
async def x_get_user_by_username(username: str) -> dict:
ctx = get_context()
resp = await ctx.dispatch("x", HttpRequest(
method=HttpMethod.GET,
path=f"/2/users/by/username/{username}?user.fields={DEFAULT_USER_FIELDS}"
))
if resp.success:
return {"data": resp.response.body.get("data")}
return {"error": resp.error.message if resp.error else "Request failed"}
@tool(
description="Search recent tweets (last 7 days)",
tags=["search", "read"],
annotations=ToolAnnotations(readOnlyHint=True),
)
async def x_search_recent(query: str, max_results: int = 10) -> dict:
from urllib.parse import quote
ctx = get_context()
max_results = max(10, min(100, max_results))
resp = await ctx.dispatch("x", HttpRequest(
method=HttpMethod.GET,
path=f"/2/tweets/search/recent?query={quote(query)}&tweet.fields={DEFAULT_TWEET_FIELDS}&max_results={max_results}"
))
if resp.success:
body = resp.response.body or {}
return {"data": body.get("data"), "meta": body.get("meta")}
return {"error": resp.error.message if resp.error else "Request failed"}
@tool(
description="Get a user's recent tweets",
tags=["tweet", "read"],
annotations=ToolAnnotations(readOnlyHint=True),
)
async def x_get_user_tweets(user_id: str, max_results: int = 10) -> dict:
ctx = get_context()
max_results = max(5, min(100, max_results))
resp = await ctx.dispatch("x", HttpRequest(
method=HttpMethod.GET,
path=f"/2/users/{user_id}/tweets?tweet.fields={DEFAULT_TWEET_FIELDS}&max_results={max_results}"
))
if resp.success:
body = resp.response.body or {}
return {"data": body.get("data"), "meta": body.get("meta")}
return {"error": resp.error.message if resp.error else "Request failed"}

Step 2: Prompt Engineering and Model Choice for CRM Agents

The secret to creating high quality agents and workflows is prompt engineering + selecting the right models and tools.

  • Define the agent’s role (for example: “You are a CRM researcher that focuses on high-value connections.”)
  • Specify success criteria (what makes a good lead or connection, research at least how many leads, etc.)
  • Explain how and when to use X tools vs web search - note that many foundation models will not by default call tools unless you explicitly specify in the prompt
  • Clearly outline the final output format (bulleted summary, relationship map, scoring, next steps)

With the right models and tools, you can virtually build anything! For example, you can adapt the same setup to:

  • Identify users who repost your account the most
  • Compare follower overlap (shared network) between different accounts
  • Categorize your followers into different buckets/ICPs
  • Search your followers and find their LinkedIn profiles
  • Score inbound leads based on social proof and activity

For this demo, we use the model Opus 4.5, which is good at:

  • Chaining multiple tool calls per run
  • Reasoning over noisy social data
  • Producing clean, CRM‑ready relationship graphs

Depending on your use case, another model or a combination of multiple models (sometimes from different providers) might be a better fit for your agent. See available model providers that Dedalus offers, including leading models from OpenAI, Google, xAI, and more.

Step 3: Using the DAuth Authentication Layer

To use DAuth to protect your X API key and secrets, you’ll need to populate the X/Twitter API connection schema and then use SecretValues to pull the environment variable from your .env file. To figure out the correct Connection() structure for your MCP, you need to research what HTTP request format the downstream API requires.

python
# Connection: schema for X/Twitter API
# Note: Some providers may also require an additional "auth_header_format" parameter in Connection(). X does not.
x = Connection(
name="x",
secrets=SecretKeys(token="X_BEARER_TOKEN"),
base_url="https://api.x.com",
)
# SecretValues: binds actual credentials to a Connection schema.
# Encrypted client-side, decrypted in secure enclave at dispatch time.
x_secrets = SecretValues(x, token=os.getenv("X_BEARER_TOKEN", ""))

Securely pass secrets to your agent via the credentials variable. Note that this variable is only needed for MCP servers that require auth.

python
...
model="anthropic/claude-opus-4-5",
mcp_servers=["tsion/brave-search-mcp", "windsor/x-api-mcp"],
credentials=[x_secrets],
...

This allows our SDK to pass your X API key to windsor/x-api-mcp without ever exposing the bearer token to the host MCP server or Dedalus.

Step 4: Streaming With the Dedalus SDK

The SDK also provides streaming support, so agents running in the command line can display their output as they work and reduce latency.

Normally, you need to write your own streaming function. Not with Dedalus. We enable streaming (for both sync and async cases) across all major model providers in one line code. Simply stream=True in the Runner.

Try Building Your Own CRM Agent With Dedalus

With the right models and tools, building the right agent only takes minutes. The CRM Agent in this demo allows you to easily conduct deep user research that would normally take hours.

This is just one example of what you can build with the Dedalus SDK and Marketplace. We encourage you to experiment with all the models and tools on Dedalus to build even more powerful agents - for every possible use case.

Relevant Links:

  • Access the GitHub repo for this project
  • Check out our docs for more tutorials
  • Join our developer community on Discord
Topics
MCPMCP ServersDedalus SDKDedalus MCP FrameworkMarketplaceDedalus Authentication LayerDAuth
Share

© 2026 Dedalus Labs. All rights reserved.