Enables management of Bluesky accounts via BlackTwist, allowing users to create and schedule posts, manage drafts and threads, and retrieve social media analytics.
Enables management of Threads accounts via BlackTwist, allowing users to create and schedule posts, manage drafts and threads, and retrieve social media analytics.
BlackTwist MCP Server
The BlackTwist MCP (Model Context Protocol) server allows AI assistants like Claude, Cursor, and other MCP-compatible clients to interact with your BlackTwist account — creating posts, checking analytics, managing drafts, and more.
Quick Start
There are two ways to connect to the BlackTwist MCP server: OAuth (recommended for claude.ai) or API Key (for desktop clients and CLI tools).
Option A: Connect via OAuth (claude.ai)
If you're using claude.ai as a custom connector, OAuth is the easiest option — no API key needed.
Go to claude.ai > Settings > Integrations (or add a custom connector)
Enter the MCP server URL:
https://blacktwist.app/api/mcpClaude will automatically discover the OAuth endpoints, register itself, and redirect you to sign in with your BlackTwist account
Once signed in, the connection is established — Claude can now use your BlackTwist account
OAuth tokens refresh automatically, so you won't need to re-authenticate unless you revoke access.
Option B: Connect via API Key
For Claude Desktop, Claude Code, Cursor, and other MCP clients that don't support OAuth, use an API key.
1. Generate an API Key
Open BlackTwist and go to Settings (gear icon)
Click the MCP tab in the sidebar
Click Create API Key, give it a name (e.g. "Claude Desktop"), and click Create Key
Copy the key immediately — it won't be shown again
2. Configure Your MCP Client
Add the following to your MCP client configuration:
Claude Desktop (claude_desktop_config.json):
{
"blacktwist": {
"command": "npx",
"args": [
"-y",
"mcp-remote@latest",
"https://blacktwist.app/api/mcp",
"--header",
"Authorization:${BLACKTWIST_TOKEN}"
],
"env": {
"BLACKTWIST_TOKEN": "Bearer YOUR_API_KEY"
}
}
}You need node.js v22, if you are using nvm be sure to use the correct version:
{
"blacktwist": {
"command": "/Users/<your_user>/.nvm/versions/node/v22.22.0/bin/npx",
"args": [
"-y",
"mcp-remote@latest",
"https://blacktwist.app/api/mcp",
"--header",
"Authorization:${BLACKTWIST_TOKEN}"
],
"env": {
"BLACKTWIST_TOKEN": "Bearer YOUR_API_KEY",
"PATH": "/Users/<your_user>/.nvm/versions/node/v22.22.0/bin:/usr/local/bin:/usr/bin:/bin",
"NODE_PATH": "/Users/<your_user>/.nvm/versions/node/v22.22.0/lib/node_modules"
}
}
}Claude Code
Run in the terminal:
claude mcp add --transport http blacktwist https://blacktwist.app/api/mcp --header "Authorization: Bearer YOUR_API_KEY"Or edit the file .mcp.json in project root:
{
"mcpServers": {
"blacktwist": {
"type": "url",
"url": "https://blacktwist.app/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}Cursor (Settings > MCP):
Name:
blacktwistType:
urlServer URL:
https://blacktwist.app/api/mcpHeaders:
Authorization: Bearer YOUR_API_KEY
3. Start Using It
Once connected, you can ask your AI assistant things like:
"List my connected social accounts"
"Schedule a post on Threads for tomorrow at 9am saying: Just shipped a new feature!"
"Edit my latest draft to say: Updated copy with better hook"
"Add a second post to my thread about product launches"
"Show me my analytics for the last 7 days"
"What are my best posting times?"
"List my upcoming scheduled posts"
Related MCP server: Social Media MCP Server
Server Details
Property | Value |
URL |
|
Auth | OAuth 2.1 (PKCE) or Bearer token (API key) |
Transport | Streamable HTTP (stateless) |
Protocol | MCP via JSON-RPC over HTTP POST |
Team Context (teamId)
Most tools accept an optional teamId parameter to scope operations to a specific team.
Behavior:
If
teamIdis a team ID, the tool operates in that team's context (membership is verified).If
teamIdis"personal", the tool operates in personal mode (user's own data only, no team).If
teamIdis omitted, the tool falls back to the user's currently active team (from user settings). If no team is active, this is equivalent to"personal".
Tools that support teamId: list_providers, list_posts, list_drafts, create_post, get_thread, delete_thread, reschedule_thread, list_time_slots, get_subscription, get_follow_up_templates, and all analytics tools.
Tools without teamId: list_teams (lists all teams), get_user_settings (personal settings), edit_post / edit_thread / get_thread_follow_up / set_thread_follow_up (thread-based access handles team auth internally via userCanAccessThread).
Subscription access: Analytics tools that require a paid plan will check the user's own subscription first. If the user doesn't have one, the system also checks whether any team owner the user belongs to has an active plan. This means team members can access paid features through their team owner's subscription.
Available Tools
Providers
list_providers
List all connected social media accounts (Threads and Bluesky).
Parameter | Type | Required | Description |
| string | No | Team ID. If not provided, uses the active team. Pass |
Returns: Array of providers with id, provider, providerUserId, providerUsername, displayName, profilePicture.
Note: Many other tools require a
providerId(theidfield) orproviderUserIdfrom this response.
Teams
list_teams
List all teams the user belongs to, including their role and which team is currently active. Always includes a "personal" entry representing the user's personal (non-team) account.
Parameters: None
Returns: Array of teams with id, name, role (OWNER, ADMIN, MEMBER, GUEST), isActive, createdAt.
The personal entry has id: "personal" and is marked isActive: true when no team is currently selected.
Note: The team
idcan be passed asteamIdto other tools (list_posts,list_drafts,create_post, etc.). Pass"personal"to explicitly use the personal account. WhenteamIdis omitted, those tools automatically use the user's currently active team.
Posts & Threads
create_post
Create a new post or thread. A thread is multiple posts linked together.
Parameter | Type | Required | Description |
| string | Yes | Provider ID from |
| array | Yes | Array of post objects (see below) |
| string | No | ISO 8601 datetime. If omitted, saves as draft. Times without a timezone offset (e.g. |
| object | No |
|
| string | No | Team ID. If not provided, uses the active team. Pass |
Default behaviors applied automatically:
Auto-repost: If
autoRepostis not provided and the user has auto-repost enabled in settings, the default delays are applied to the first post.Follow-up (auto-plug): If the provider has a default follow-up template enabled, it is automatically copied and attached to the thread. The response includes
autoPlugApplied: truewhen this happens.Timezone: Times without an offset are interpreted in the user's
notificationsTimezonesetting.
Each post object:
Field | Type | Required | Description |
| string | Yes | Post content |
| string | No | Topic tag |
| object[] | No | Media attachments: |
Example — single post:
{
"providerId": "clx...",
"posts": [{ "text": "Hello world!" }],
"scheduleAt": "2025-03-15T09:00:00"
}Example — thread:
{
"providerId": "clx...",
"posts": [
{ "text": "Thread time! Here's what I learned this week 🧵" },
{ "text": "1/ First lesson: consistency beats perfection" },
{ "text": "2/ Second lesson: engage with your community daily" }
],
"scheduleAt": "2025-03-15T09:00:00"
}list_posts
List scheduled and published posts within a date range.
Parameter | Type | Required | Description |
| string | Yes | Start date (ISO 8601) |
| string | Yes | End date (ISO 8601) |
| string | No | Provider ID (from |
| string | No | Team ID. If not provided, uses the active team. Pass |
list_drafts
List all draft posts (not yet scheduled).
Parameter | Type | Required | Description |
| string | No | Team ID. If not provided, uses the active team. Pass |
get_thread
Get a specific thread with all its posts, media, and analytics.
Parameter | Type | Required | Description |
| string | Yes | The thread ID |
| string | No | Team ID. If not provided, uses the active team. Pass |
edit_post
Edit the text content, topic, or media of a single post. Only posts with status READY (drafts or scheduled) can be edited — published, processing, or failed posts are rejected. Use get_thread first to retrieve post IDs.
Parameter | Type | Required | Description |
| string | Yes | The post ID (from |
| string | No | New text content |
| string | No | New topic tag. Pass |
| object[] | No | Replace all media: |
At least one of content, topic, or postMedia must be provided.
Returns: postId, threadId, updated (object indicating which fields were changed).
Example — update text:
{
"postId": "clx...",
"content": "Updated post content"
}Example — update text and clear topic:
{
"postId": "clx...",
"content": "New content",
"topic": null
}edit_thread
Edit a thread by adding, removing, or reordering its posts. Provide the full desired list of posts — existing posts (with id) are updated, new posts (without id) are created, and existing posts not in the list are removed. Only threads where all posts have status READY can be edited.
Parameter | Type | Required | Description |
| string | Yes | The thread ID |
| array | Yes | Ordered array of posts (min 1). Order determines |
Each post object:
Field | Type | Required | Description |
| string | No | Post ID for existing posts. Omit to create a new post in this position. |
| string | Yes | Post content |
| string | No | Topic tag |
| object[] | No | Media attachments: |
New posts inherit scheduledAt, provider, providerUserId, and teamId from the existing thread.
Returns: threadId, postsUpdated, postsCreated, postsDeleted, totalPosts.
Example — reorder and add a post:
{
"threadId": "abc123",
"posts": [
{ "id": "existing-post-2", "text": "Now this is first" },
{ "id": "existing-post-1", "text": "Now this is second" },
{ "text": "Brand new third post" }
]
}Example — remove a post (omit it from the list):
{
"threadId": "abc123",
"posts": [{ "id": "existing-post-1", "text": "Keep only this one" }]
}delete_thread
Permanently delete a thread and all its posts.
Parameter | Type | Required | Description |
| string | Yes | The thread ID |
| string | No | Team ID. If not provided, uses the active team. Pass |
reschedule_thread
Change the scheduled date/time for a thread.
Parameter | Type | Required | Description |
| string | Yes | The thread ID |
| string | Yes | New datetime (ISO 8601). Times without a timezone offset are interpreted in the user's configured timezone. |
| string | No | Team ID. If not provided, uses the active team. Pass |
Analytics
All analytics tools require a providerUserId (from list_providers) and accept an optional teamId to scope access.
Paid plan required:
get_metric_timeseries,get_post_analytics, andget_follower_growthrequire a paid plan (user or team owner). Free-plan users will receive: "A paid plan is required to access the analytics". The remaining tools (get_live_metrics,get_consistency,get_daily_recap,get_recommendations) are available on all plans.Date ranges: The
fromandtodates are both inclusive. For example,from: "2026-03-01"andto: "2026-03-08"includes all data from March 1 through the end of March 8.
get_live_metrics
Get engagement metrics with percentage change vs. the previous period.
Parameter | Type | Required | Description |
| string | Yes | Provider user ID |
| string | No | Start date (defaults to 7 days ago) |
| string | No | End date (defaults to now) |
| string | No | Team ID. If not provided, uses the active team. Pass |
Returns: views, likes, replies, reposts, quotes — each with value and percentageChange.
get_metric_timeseries
Get daily data points for a specific metric over a date range.
Parameter | Type | Required | Description |
| string | Yes | Provider user ID |
| enum | Yes | One of: |
| string | Yes | Start date (ISO 8601) |
| string | Yes | End date (ISO 8601) |
| string | No | Team ID. If not provided, uses the active team. Pass |
get_post_analytics
Get per-post engagement metrics for posts within a date range.
Parameter | Type | Required | Description |
| string | Yes | Provider user ID |
| string | Yes | Start date (ISO 8601) |
| string | Yes | End date (ISO 8601) |
| string | No | Team ID. If not provided, uses the active team. Pass |
Returns: Up to 100 posts with views, likes, replies, reposts, quotes, engagementRate.
get_follower_growth
Get daily follower count over time.
Parameter | Type | Required | Description |
| string | Yes | Provider user ID |
| string | Yes | Start date (ISO 8601) |
| string | Yes | End date (ISO 8601) |
| string | No | Team ID. If not provided, uses the active team. Pass |
Returns: Daily followers data points and totalGrowth.
get_consistency
Get a 365-day posting consistency heatmap.
Parameter | Type | Required | Description |
| string | Yes | Provider user ID |
| string | No | Team ID. If not provided, uses the active team. Pass |
Returns: days (array of { date, count }), totalPosts, daysWithPosts, currentStreak, recordStreak.
get_daily_recap
Get yesterday's summary.
Parameter | Type | Required | Description |
| string | Yes | Provider user ID |
| string | No | Team ID. If not provided, uses the active team. Pass |
Returns: date, newFollowers, postsCount.
get_recommendations
Get posting recommendations based on your analytics patterns.
Parameter | Type | Required | Description |
| string | Yes | Provider user ID |
| string | No | Team ID. If not provided, uses the active team. Pass |
Returns: bestPostingHoursUtc, topPerformingPosts, currentStreak, recordStreak.
Follow-Up (Auto-Plug)
The follow-up system automatically replies to your post after it reaches engagement thresholds (likes, replies, reposts) or a time delay.
get_follow_up_templates
List saved follow-up templates for a provider.
Parameter | Type | Required | Description |
| string | Yes | Provider |
| string | No | Team ID. If not provided, uses the active team. Pass |
get_thread_follow_up
Get the follow-up configuration for a specific thread.
Parameter | Type | Required | Description |
| string | Yes | The thread ID |
set_thread_follow_up
Set or update the follow-up for a thread.
Parameter | Type | Required | Description |
| string | Yes | The thread ID |
| boolean | Yes | Enable/disable the follow-up |
| string | No | Follow-up reply text |
| string[] | No | Image URLs |
| number | No | Time delay trigger (minutes) |
| boolean | No | Enable time delay trigger |
| number | No | Likes threshold |
| boolean | No | Enable likes trigger |
| number | No | Replies threshold |
| boolean | No | Enable replies trigger |
| number | No | Reposts threshold |
| boolean | No | Enable reposts trigger |
| object[] | No | Media attachments: |
Scheduling
list_time_slots
List configured posting time slots for a provider. Time slots define preferred scheduling times.
Parameter | Type | Required | Description |
| string | Yes | Provider |
| string | No | Team ID. If not provided, uses the active team. Pass |
Returns: Array of { timeSlotHour, timeSlotMinute, weekDays } where weekDays is [0-6] (0 = Sunday).
Account
get_subscription
Get your current plan, remaining post limits, and account quotas.
Parameter | Type | Required | Description |
| string | No | Team ID. If not provided, uses the active team. Pass |
Returns: planType, planName, isFreePlan, remainingPosts, limits (maxTeams, maxMembers, maxAccounts).
get_user_settings
Get your user settings including timezone, date format, and auto-repost config.
Parameters: None
Returns: notificationsTimezone, dateFormat, autoRepostEnabled, autoRepostDelays, and more.
Common Workflows
Schedule a post at your next free time slot
list_providers→ get your provider IDlist_time_slots→ find available slotscreate_post→ schedule with the next slot time
Check weekly performance
list_providers→ get your provider user IDget_live_metrics→ see this week vs. last weekget_recommendations→ get improvement suggestions
Edit an existing post
get_thread→ retrieve the thread and its post IDsedit_post→ update the text, topic, or media of a specific post
Add a post to an existing thread
get_thread→ retrieve current posts with their IDsedit_thread→ pass all existing posts (with IDs) plus the new post (without ID)
Create a post with follow-up
create_post→ create and schedule the post (returnsthreadId)set_thread_follow_up→ attach a promotional reply
Prompts
The MCP server includes built-in prompts — reusable templates that guide your AI assistant through multi-step workflows. In Claude Desktop, access them by typing / in the chat. In Claude Code, run claude prompts to list them.
weekly-report
Generate a weekly performance report with engagement metrics, top posts, follower growth, and recommendations.
Arguments:
Parameter | Required | Description |
| Yes | Your provider user ID |
Example: "Run the weekly-report prompt for my Threads account"
content-ideas
Generate post ideas based on your best-performing content and posting patterns.
Arguments:
Parameter | Required | Description |
| Yes | Your provider user ID |
| No | Topic to focus on (e.g. "productivity", "startups") |
Example: "Give me content ideas about marketing"
optimize-schedule
Analyze your posting patterns and suggest an optimal posting schedule for the week.
Arguments:
Parameter | Required | Description |
| Yes | Your provider user ID |
| Yes | Your provider ID |
Example: "Help me optimize my posting schedule"
draft-review
Review your current drafts and get suggestions to improve them before publishing.
Arguments: None
Example: "Review my drafts and tell me how to improve them"
monthly-recap
Generate a comprehensive monthly recap with key metrics, top posts, and growth trends.
Arguments:
Parameter | Required | Description |
| Yes | Your provider user ID |
Example: "Generate my monthly recap"
API Key Management
Create keys: Settings > MCP > Create API Key
View keys: Settings > MCP (shows prefix, creation date, last used)
Delete keys: Click the trash icon next to any key
Endpoint:
GET/POST/DELETE /api/user/mcp-keys
Keys are hashed with SHA-256 before storage. The raw key is only shown once at creation time.
Authentication
The MCP server supports two authentication methods. Both use the Authorization: Bearer <token> header.
OAuth 2.1 (for claude.ai and OAuth-capable clients)
The server implements the MCP Authorization specification with OAuth 2.1 and PKCE (S256). OAuth-capable clients like claude.ai handle the flow automatically.
OAuth endpoints:
Endpoint | Description |
| Resource metadata (RFC 9728) |
| Authorization server metadata (RFC 8414) |
| Dynamic client registration (RFC 7591) |
| Authorization endpoint |
| Token exchange and refresh |
| Token revocation (RFC 7009) |
How it works:
The client discovers the OAuth endpoints via the well-known metadata
The client registers itself using dynamic client registration
The user is redirected to sign in with their BlackTwist account (uses the existing BlackTwist login)
After sign-in, an authorization code is issued and exchanged for access + refresh tokens
Access tokens expire after 1 hour and are refreshed automatically using the refresh token (30-day lifetime)
API Keys (for desktop clients and CLI tools)
API keys are static tokens prefixed with bt_mcp_. They don't expire by default and are suitable for clients that don't support OAuth (Claude Desktop, Cursor, Claude Code CLI).
See API Key Management for how to create and manage keys.
Technical Details
Endpoint:
POST /api/mcp(JSON-RPC)Transport:
WebStandardStreamableHTTPServerTransportfrom@modelcontextprotocol/sdkMode: Stateless (no session management)
Auth: OAuth 2.1 with PKCE (S256) or API key via
Authorization: Bearer <key>headerSource code:
src/libs/mcp/(server, auth, oauth, tools),src/app/api/mcp/(route handler, OAuth endpoints)