Design system MCP server for creating high-performing LinkedIn content Quick Start • Installation • Documentation • A professional Model Context Protocol (MCP) server for LinkedIn content creation, featuring a shadcn-inspired component system, 10 performance-tuned themes, and data-driven optimization based on 1M+ post analysis. Built on ChukMCPServer — a modular, zero-configuration MCP server fram
Add this skill
npx mdskills install IBM/chuk-mcp-linkedinWell-architected LinkedIn content MCP server with OAuth 2.1, component system, and strong security practices
Design system MCP server for creating high-performing LinkedIn content
Features • Quick Start • Installation • Documentation • Examples
A professional Model Context Protocol (MCP) server for LinkedIn content creation, featuring a shadcn-inspired component system, 10 performance-tuned themes, and data-driven optimization based on 1M+ post analysis.
Built on ChukMCPServer — a modular, zero-configuration MCP server framework with smart environment detection and production-ready defaults.
What it does:
What it doesn't do:
Token Security:
Draft Isolation:
@requires_auth decorator)Artifact Storage:
Session Management:
OAuth 2.1 Compliance (RFC 9728):
/.well-known/oauth-authorization-server/.well-known/oauth-protected-resourceOAuth Modes:
LinkedIn API Compliance: You are responsible for complying with LinkedIn's API Terms of Service and rate limits. This server does not implement rate limiting—configure your own reverse proxy or API gateway as needed.
Based on 2025 analysis of 1M+ posts across 9K company pages:
Data & Methodology
Dataset: 1,042,183 posts from 9,247 company pages (Jan–Dec 2025)
Metrics:
Sources: LinkedIn Pages API, aggregated from opted-in company accounts. Engagement rates are median values to reduce outlier bias. Timing analysis uses UTC-normalized timestamps.
Limitations: Dataset skews toward B2B tech companies (63% of sample). Results may vary for consumer brands or regional markets.
linkedin-mcp and linkedin-mcp-server commandsThe easiest way to get started is to use our hosted MCP server at https://linkedin.chukai.io.
Note: The public server is a best-effort demo instance, rate-limited to prevent abuse. For production use with guaranteed SLA, deploy your own instance (see Deployment).
Add to Claude Desktop:
Open your Claude Desktop configuration file:
~/Library/Application Support/Claude/claude_desktop_config.jsonC:\Users\\AppData\Roaming\Claude\claude_desktop_config.json(Replace `` with your actual Windows username)
Add the LinkedIn MCP server (no trailing slash):
{
"mcpServers": {
"linkedin": {
"url": "https://linkedin.chukai.io"
}
}
}
Restart Claude Desktop
Authenticate with LinkedIn when prompted (you'll be redirected to LinkedIn OAuth)
Use with MCP CLI:
# Install MCP CLI (using uvx - no separate install needed)
# Requires: ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable
# Connect with Claude
uvx mcp-cli --server https://linkedin.chukai.io --provider anthropic --model claude-sonnet-4-5
# Or with OpenAI
uvx mcp-cli --server https://linkedin.chukai.io --provider openai --model gpt-5-mini
# Or use local Ollama (no API key needed)
uvx mcp-cli --server https://linkedin.chukai.io
The public server includes:
Want to run your own instance? Install and run the server locally:
1. Install the Package
# Basic installation
pip install chuk-mcp-linkedin
# With HTTP server support
pip install chuk-mcp-linkedin[http]
# With document preview support
pip install chuk-mcp-linkedin[preview]
# For development
pip install chuk-mcp-linkedin[dev]
2. Set Up Environment Variables
Create a .env file:
# LinkedIn OAuth credentials (required)
LINKEDIN_CLIENT_ID=your_client_id
LINKEDIN_CLIENT_SECRET=your_client_secret
LINKEDIN_REDIRECT_URI=http://localhost:8000/oauth/callback
# Optional: OAuth server URL (for discovery endpoint)
OAUTH_SERVER_URL=http://localhost:8000
# Session storage (default: memory)
SESSION_PROVIDER=memory
# Enable publishing (default: false)
ENABLE_PUBLISHING=true
3. Run the Server
# STDIO mode (for Claude Desktop)
linkedin-mcp stdio
# HTTP mode (API server)
linkedin-mcp http --port 8000
# Auto-detect mode
linkedin-mcp auto
# With debug logging
linkedin-mcp stdio --debug
4. Configure Claude Desktop (Local Server)
{
"mcpServers": {
"linkedin": {
"command": "linkedin-mcp",
"args": ["stdio"],
"env": {
"LINKEDIN_CLIENT_ID": "your_client_id",
"LINKEDIN_CLIENT_SECRET": "your_client_secret"
}
}
}
}
from chuk_mcp_linkedin.posts import ComposablePost
from chuk_mcp_linkedin.themes import ThemeManager
# Get a theme
theme = ThemeManager().get_theme("thought_leader")
# Compose a post
post = ComposablePost("text", theme=theme)
post.add_hook("stat", "95% of LinkedIn posts get zero comments")
post.add_body("""
Here's why (and how to fix it):
Most posts lack these 3 elements:
→ Strong hook (first 210 characters)
→ Clear value (what's in it for them)
→ Conversation starter (invite engagement)
Start treating posts like conversations, not broadcasts.
""", structure="listicle")
post.add_cta("curiosity", "What's your biggest LinkedIn frustration?")
post.add_hashtags(["LinkedInTips", "ContentStrategy"])
# Get the composed text
text = post.compose()
print(text)
# Basic installation (STDIO mode only)
pip install chuk-mcp-linkedin
# Recommended: with uv (faster, more reliable)
uv pip install chuk-mcp-linkedin
Install additional features as needed:
| Extra | Command | Includes | Use Case |
|---|---|---|---|
| http | pip install chuk-mcp-linkedin[http] | uvicorn, starlette | Run as HTTP API server |
| preview | pip install chuk-mcp-linkedin[preview] | pdf2image, Pillow, python-pptx, python-docx, PyPDF2 | Document preview rendering |
| dev | pip install chuk-mcp-linkedin[dev] | pytest, black, ruff, mypy, pre-commit | Development & testing |
| all | pip install "chuk-mcp-linkedin[dev,http,preview]" | All of the above | Full installation |
System Dependencies (Preview Support):
# macOS
brew install poppler
# Ubuntu/Debian
sudo apt-get install poppler-utils
# Windows (using Chocolatey)
choco install poppler
# Clone from your source repository
uv pip install -e ".[dev,http,preview]"
# Get help
linkedin-mcp --help
# STDIO mode (for Claude Desktop)
linkedin-mcp stdio
# HTTP mode (API server on port 8000)
linkedin-mcp http --host 0.0.0.0 --port 8000
# Auto-detect best mode
linkedin-mcp auto
# Enable debug logging
linkedin-mcp stdio --debug --log-level DEBUG
from chuk_mcp_linkedin.posts import ComposablePost
from chuk_mcp_linkedin.themes import ThemeManager
# Get theme
theme_mgr = ThemeManager()
theme = theme_mgr.get_theme("thought_leader")
# Create post
post = ComposablePost("text", theme=theme)
post.add_hook("question", "What drives innovation in 2025?")
post.add_body("Innovation comes from diverse perspectives...", structure="linear")
post.add_cta("direct", "Share your thoughts!")
# Compose final text
final_text = post.compose()
Document posts have 45.85% engagement rate - the highest format in 2025!
from chuk_mcp_linkedin.posts import ComposablePost
# Compose post text (publishing via MCP server with OAuth)
post = ComposablePost("document", theme=theme)
post.add_hook("stat", "Document posts get 45.85% engagement")
post.add_body("Our Q4 results are in. Here's what we learned 📊")
post.add_cta("curiosity", "What's your biggest takeaway?")
text = post.compose()
# Publishing is done via MCP server tools with OAuth authentication
# See examples/oauth_linkedin_example.py for OAuth flow
# See docs/OAUTH.md for setup instructions
Polls get 200%+ higher reach than average posts!
# Create poll
post = ComposablePost("poll", theme=theme)
post.add_hook("question", "Quick question for my network:")
post.add_body("What's your biggest LinkedIn challenge in 2025?")
# Note: Actual poll creation uses LinkedIn API
# This creates the post text; poll options go via API
Preview your posts before publishing with automatic URL detection:
from chuk_mcp_linkedin.manager import LinkedInManager
manager = LinkedInManager()
# Create draft
draft = manager.create_draft("My Post", "text")
# ... compose post ...
# Generate HTML preview (auto-opens in browser)
preview_path = manager.generate_html_preview(draft.draft_id)
MCP Tool: linkedin_preview_url
Generate shareable preview URLs with automatic server detection:
# Via MCP tool
{
"tool": "linkedin_preview_url",
"arguments": {
"draft_id": "draft_123" # Optional, uses current draft if not provided
}
}
Preview URL Behavior:
OAUTH_SERVER_URL env var
https://linkedin.chukai.io/preview/abc123http://localhost:8000/preview/abc123base_url parameter if neededEnvironment Variables:
# Production - preview URLs use this automatically
export OAUTH_SERVER_URL=https://linkedin.chukai.io
# Local - no configuration needed (defaults to localhost:8000)
CLI Preview (Legacy):
# Preview current draft
python preview_post.py
# Preview specific draft
python preview_post.py draft_id_here
# List all drafts
python preview_post.py --list
The server includes enterprise-grade session management and artifact storage powered by chuk-artifacts:
Features:
from chuk_mcp_linkedin.manager import LinkedInManager
# Create manager with session ID
manager = LinkedInManager(
session_id="user_alice",
use_artifacts=True,
artifact_provider="memory" # or "filesystem", "s3", "ibm-cos"
)
# Drafts are automatically locked to this session
draft = manager.create_draft("My Post", "text")
# Only this session can access the draft
accessible = manager.is_draft_accessible(draft.draft_id) # True for "user_alice"
# Different session cannot access
other_manager = LinkedInManager(session_id="user_bob")
accessible = other_manager.is_draft_accessible(draft.draft_id) # False
Generate secure preview URLs with automatic expiration:
from chuk_mcp_linkedin.preview import get_artifact_manager
# Initialize artifact manager
async with await get_artifact_manager(provider="memory") as artifacts:
# Create session
session_id = artifacts.create_session(user_id="alice")
# Store preview
artifact_id = await artifacts.store_preview(
html_content="...",
draft_id="draft_123",
draft_name="My Post",
session_id=session_id
)
# Generate presigned URL (expires in 1 hour)
url = await artifacts.get_preview_url(
artifact_id=artifact_id,
session_id=session_id,
expires_in=3600
)
print(f"Preview URL: {url}")
The linkedin_preview_url tool generates session-isolated preview URLs:
{
"tool": "linkedin_preview_url",
"arguments": {
"draft_id": "draft_123", // optional: defaults to current draft
"base_url": "https://linkedin.chukai.io", // optional: auto-detected from OAUTH_SERVER_URL
"expires_in": 3600 // optional: default 3600s
}
}
Response:
{
"url": "https://linkedin.chukai.io/preview/04a0c703d98d428fae0e550c885523f7",
"draft_id": "draft_123",
"artifact_id": "04a0c703d98d428fae0e550c885523f7",
"expires_in": 3600
}
The URL is shareable and does not require authentication. It will expire automatically after the specified time.
Configure storage backend based on your needs:
Memory (Default):
# Fast, ephemeral storage for development
manager = LinkedInManager(use_artifacts=True, artifact_provider="memory")
Filesystem:
# Persistent storage on disk
manager = LinkedInManager(use_artifacts=True, artifact_provider="filesystem")
# Stores in: .artifacts/linkedin-drafts/
S3:
# Configure via environment variables
export ARTIFACT_PROVIDER=s3
export ARTIFACT_S3_BUCKET=my-linkedin-artifacts
export ARTIFACT_S3_REGION=us-east-1
export AWS_ACCESS_KEY_ID=your_key
export AWS_SECRET_ACCESS_KEY=your_secret
from chuk_artifacts.config import configure_s3
# Or configure programmatically
configure_s3(
bucket="my-linkedin-artifacts",
region="us-east-1",
access_key="your_key",
secret_key="your_secret"
)
manager = LinkedInManager(use_artifacts=True, artifact_provider="s3")
IBM Cloud Object Storage:
from chuk_artifacts.config import configure_ibm_cos
configure_ibm_cos(
bucket="my-linkedin-artifacts",
endpoint="https://s3.us-south.cloud-object-storage.appdomain.cloud",
access_key="your_key",
secret_key="your_secret"
)
Artifacts use a hierarchical grid structure:
grid/
├── {sandbox_id}/ # "linkedin-mcp"
│ ├── {session_id}/ # "user_alice"
│ │ ├── {artifact_id}/ # "abc123"
│ │ │ ├── metadata.json
│ │ │ └── content
│ │ └── {artifact_id}/
│ └── {session_id}/
└── {sandbox_id}/
This ensures:
For local development without cloud storage:
# Use in-memory artifact storage
from chuk_mcp_linkedin.manager import LinkedInManager
manager = LinkedInManager(
use_artifacts=True,
artifact_provider="memory" # Fast, ephemeral storage
)
# Or use filesystem for persistent local storage
manager = LinkedInManager(
use_artifacts=True,
artifact_provider="filesystem" # Stores in .artifacts/
)
10 pre-built themes for different LinkedIn personas:
| Theme | Description | Use Case |
|---|---|---|
thought_leader | Authority and expertise | Industry insights, frameworks |
data_driven | Let numbers tell story | Analytics, research, reports |
storyteller | Narrative-driven | Personal experiences, case studies |
community_builder | Foster conversation | Polls, questions, engagement |
technical_expert | Deep technical knowledge | Engineering, dev, technical topics |
personal_brand | Authentic connection | Behind-the-scenes, personal stories |
corporate_professional | Polished corporate | Official announcements, updates |
contrarian_voice | Challenge status quo | Controversial takes, debate |
coach_mentor | Guide and support | Tips, advice, mentorship |
entertainer | Make LinkedIn fun | Humor, memes, light content |
For HTTP mode with OAuth authentication:
{
"mcpServers": {
"linkedin": {
"command": "linkedin-mcp",
"args": ["http", "--port", "8000"],
"env": {
"SESSION_PROVIDER": "memory",
"LINKEDIN_CLIENT_ID": "your_linkedin_client_id",
"LINKEDIN_CLIENT_SECRET": "your_linkedin_client_secret",
"OAUTH_ENABLED": "true"
}
}
}
}
Then use with MCP-CLI:
uvx mcp-cli --server linkedin --provider openai --model gpt-5-mini
See docs/OAUTH.md for complete OAuth setup instructions.
For Claude Desktop and other desktop client integration:
{
"mcpServers": {
"linkedin": {
"command": "linkedin-mcp",
"args": ["stdio"]
}
}
}
Note: OAuth is required for publishing tools. STDIO mode supports all other tools (drafting, composition, previews).
# Build image
docker build -t chuk-mcp-linkedin:latest .
# Run in STDIO mode
docker-compose --profile stdio up -d
# Run in HTTP mode
docker-compose --profile http up -d
# View logs
docker-compose logs -f
make docker-build # Build Docker image
make docker-run-stdio # Run in STDIO mode
make docker-run-http # Run in HTTP mode on port 8000
make docker-test # Build and test image
make docker-logs # View container logs
make docker-stop # Stop containers
make docker-clean # Clean up Docker resources
Create a .env file:
# ============================================================================
# OAuth Configuration (Required for Publishing)
# ============================================================================
# LinkedIn OAuth Credentials (from https://www.linkedin.com/developers/apps)
LINKEDIN_CLIENT_ID=your_linkedin_client_id
LINKEDIN_CLIENT_SECRET=your_linkedin_client_secret
# OAuth Server URLs
LINKEDIN_REDIRECT_URI=http://localhost:8000/oauth/callback # Must match LinkedIn app settings
OAUTH_SERVER_URL=http://localhost:8000
OAUTH_ENABLED=true
# Session Storage (for OAuth tokens)
SESSION_PROVIDER=memory # Development: memory | Production: redis
SESSION_REDIS_URL=redis://localhost:6379/0 # Required if SESSION_PROVIDER=redis
# ============================================================================
# OAuth Token TTL Configuration (Optional - Defaults Shown)
# ============================================================================
# Authorization codes - Temporary codes exchanged for access tokens during OAuth flow
# Short-lived for security (5 minutes)
OAUTH_AUTH_CODE_TTL=300
# Access tokens - Used by MCP clients to authenticate API requests
# Should be short-lived and refreshed regularly (15 minutes)
OAUTH_ACCESS_TOKEN_TTL=900
# Refresh tokens - Long-lived tokens that obtain new access tokens without re-authentication
# Short lifetime requires daily re-authorization for maximum security (1 day)
OAUTH_REFRESH_TOKEN_TTL=86400
# Client registrations - How long dynamically registered MCP clients remain valid (1 year)
OAUTH_CLIENT_REGISTRATION_TTL=31536000
# LinkedIn tokens - Access and refresh tokens from LinkedIn stored server-side
# Auto-refreshed when expired (1 day, more secure than LinkedIn's 60-day default)
OAUTH_EXTERNAL_TOKEN_TTL=86400
# ============================================================================
# Server Configuration
# ============================================================================
DEBUG=0
HTTP_PORT=8000
# LinkedIn Person URN (for API calls - auto-detected from OAuth token)
LINKEDIN_PERSON_URN=urn:li:person:YOUR_ID # Optional: Auto-fetched via OAuth
Key Points:
linkedin_publish) require OAuth authenticationSee docs/OAUTH.md for complete OAuth setup and docs/DOCKER.md for Docker deployment.
Deploy the LinkedIn MCP server to Fly.io with Redis session storage:
curl -L https://fly.io/install.sh | sh# Login to Fly.io
fly auth login
# Create app (generates fly.toml)
fly launch --no-deploy
# Choose app name (e.g., your-linkedin-mcp)
# Choose region (e.g., cdg for Paris)
# Create Redis on Fly.io
fly redis create
# Note the Redis URL from output:
# redis://default:PASSWORD@fly-INSTANCE-NAME.upstash.io:6379
# Create Tigris S3-compatible storage for preview artifacts
fly storage create --name your-linkedin-mcp
# Fly automatically sets these secrets on your app:
# - AWS_ACCESS_KEY_ID
# - AWS_SECRET_ACCESS_KEY
# - AWS_ENDPOINT_URL_S3
# - AWS_REGION
# - BUCKET_NAME
Required Secrets Reference:
| Secret | Required | Source | Purpose |
|---|---|---|---|
LINKEDIN_CLIENT_ID | ✅ Yes | LinkedIn Developers Portal | OAuth client ID |
LINKEDIN_CLIENT_SECRET | ✅ Yes | LinkedIn Developers Portal | OAuth client secret |
SESSION_REDIS_URL | ✅ Yes | Output from fly redis create (Step 2) | Redis connection string for sessions |
SESSION_PROVIDER | ✅ Yes | Set to redis | Enable Redis session backend |
OAUTH_SERVER_URL | ✅ Yes | Your Fly.io app URL | OAuth discovery base URL |
LINKEDIN_REDIRECT_URI | ✅ Yes | {OAUTH_SERVER_URL}/oauth/callback | OAuth callback endpoint |
AWS_ACCESS_KEY_ID | Auto | fly storage create (Step 3) | Tigris S3 access key (auto-set) |
AWS_SECRET_ACCESS_KEY | Auto | fly storage create (Step 3) | Tigris S3 secret (auto-set) |
AWS_ENDPOINT_URL_S3 | Auto | fly storage create (Step 3) | Tigris S3 endpoint (auto-set) |
AWS_REGION | Auto | fly storage create (Step 3) | Tigris S3 region (auto-set) |
Set required secrets with Fly CLI:
# LinkedIn OAuth credentials (from https://www.linkedin.com/developers/apps)
fly secrets set \
LINKEDIN_CLIENT_ID=your_linkedin_client_id \
LINKEDIN_CLIENT_SECRET=your_linkedin_client_secret \
--app your-linkedin-mcp
# Redis connection (from step 2)
fly secrets set \
SESSION_REDIS_URL="redis://default:PASSWORD@fly-INSTANCE-NAME.upstash.io:6379" \
SESSION_PROVIDER=redis \
--app your-linkedin-mcp
# OAuth server configuration
fly secrets set \
OAUTH_SERVER_URL=https://your-linkedin-mcp.fly.dev \
LINKEDIN_REDIRECT_URI=https://your-linkedin-mcp.fly.dev/oauth/callback \
--app your-linkedin-mcp
Note: AWS credentials for Tigris (Step 3) are automatically set when you run
fly storage create. No manual configuration needed!
Update fly.toml with production settings:
app = 'your-linkedin-mcp'
primary_region = 'cdg'
[build]
[http_service]
internal_port = 8000
force_https = true
auto_stop_machines = 'stop'
auto_start_machines = true
min_machines_running = 0
processes = ['app']
[[vm]]
memory = '1gb'
cpu_kind = 'shared'
cpus = 1
[env]
SESSION_PROVIDER = 'redis'
ENABLE_PUBLISHING = true
OAUTH_SERVER_URL = 'https://your-linkedin-mcp.fly.dev'
LINKEDIN_REDIRECT_URI = 'https://your-linkedin-mcp.fly.dev/oauth/callback'
# Artifact Storage (Tigris S3-compatible)
ARTIFACT_PROVIDER = 's3'
ARTIFACT_S3_BUCKET = 'your-linkedin-mcp'
# AWS_* secrets automatically set by `fly storage create`
# Deploy to Fly.io
fly deploy
# Check deployment status
fly status
# View logs
fly logs
# Test OAuth endpoint
curl https://your-linkedin-mcp.fly.dev/.well-known/oauth-authorization-server
Update your MCP client configuration (e.g., ~/.mcp-cli/servers.yaml):
servers:
linkedin:
url: https://your-linkedin-mcp.fly.dev # No trailing slash!
oauth: true
Test the connection:
uvx mcp-cli --server linkedin --provider openai --model gpt-5-mini
For local development, use in-memory session storage:
# .env file
SESSION_PROVIDER=memory
No Redis installation required. Sessions are lost when the server restarts.
For production, use Redis for persistent session storage:
Option 1: Fly.io Redis (Upstash)
# Create Redis instance
fly redis create
# Get connection details
fly redis status your-redis-instance
# Set as secret
fly secrets set SESSION_REDIS_URL="redis://default:PASSWORD@fly-INSTANCE.upstash.io:6379"
Option 2: External Redis (Upstash, AWS ElastiCache, etc.)
# Set Redis URL
export SESSION_REDIS_URL="redis://username:password@host:port/db"
export SESSION_PROVIDER=redis
Environment Variables:
# Session Provider
SESSION_PROVIDER=redis # Required: redis | memory
# Redis Connection (required if SESSION_PROVIDER=redis)
SESSION_REDIS_URL=redis://default:password@host:6379
# Optional Redis settings
REDIS_TLS_INSECURE=0 # Set to 1 to disable TLS cert verification (not recommended)
Configure a custom domain for your deployment:
# Add custom domain
fly certs create linkedin.yourdomain.com
# Verify DNS settings
fly certs show linkedin.yourdomain.com
Add DNS records (check output from previous command):
Type: CNAME
Name: linkedin.yourdomain.com
Value: your-linkedin-mcp.fly.dev
# Update secrets with custom domain
fly secrets set \
OAUTH_SERVER_URL=https://linkedin.yourdomain.com \
LINKEDIN_REDIRECT_URI=https://linkedin.yourdomain.com/oauth/callback
https://linkedin.yourdomain.com/oauth/callbackComplete list of production environment variables:
# ============================================================================
# OAuth Configuration (Required for Production)
# ============================================================================
# LinkedIn OAuth Credentials
LINKEDIN_CLIENT_ID=your_linkedin_client_id
LINKEDIN_CLIENT_SECRET=your_linkedin_client_secret
# OAuth Server URLs (must match LinkedIn app settings)
# IMPORTANT: This URL is also used for preview URLs (linkedin_preview_url tool)
OAUTH_SERVER_URL=https://your-app.fly.dev
LINKEDIN_REDIRECT_URI=https://your-app.fly.dev/oauth/callback
OAUTH_ENABLED=true
# ============================================================================
# Session Storage (Required for Production)
# ============================================================================
# Production: Use Redis
SESSION_PROVIDER=redis
SESSION_REDIS_URL=redis://default:password@fly-instance.upstash.io:6379
# Development: Use Memory
# SESSION_PROVIDER=memory
# ============================================================================
# OAuth Token TTL Configuration (Optional - Defaults Shown)
# ============================================================================
OAUTH_AUTH_CODE_TTL=300 # Authorization codes (5 min)
OAUTH_ACCESS_TOKEN_TTL=900 # Access tokens (15 min)
OAUTH_REFRESH_TOKEN_TTL=86400 # Refresh tokens (1 day)
OAUTH_CLIENT_REGISTRATION_TTL=31536000 # Client registrations (1 year)
OAUTH_EXTERNAL_TOKEN_TTL=86400 # LinkedIn tokens (1 day)
# ============================================================================
# Server Configuration
# ============================================================================
DEBUG=0 # Disable debug mode in production
HTTP_PORT=8000 # Server port
ENABLE_PUBLISHING=true # Enable publishing tools
# LinkedIn Person URN (optional - auto-detected via OAuth)
LINKEDIN_PERSON_URN=urn:li:person:YOUR_ID
Control logging levels in production:
# Production logging
LOG_LEVEL=INFO # INFO for production, DEBUG for troubleshooting
MCP_LOG_LEVEL=WARNING # MCP protocol logging
# Development logging
LOG_LEVEL=DEBUG
MCP_LOG_LEVEL=INFO
Security Note: At INFO level, sensitive data (tokens, user IDs, authorization codes) is NOT logged. This data is only logged at DEBUG level for troubleshooting.
# View live logs
fly logs --app your-linkedin-mcp
# Check app status
fly status --app your-linkedin-mcp
# Check Redis status
fly redis status your-redis-instance
# Restart app
fly apps restart your-linkedin-mcp
# Scale app
fly scale count 2 --app your-linkedin-mcp # 2 instances
fly scale memory 2048 --app your-linkedin-mcp # 2GB memory
The server includes health check endpoints:
# Check server health
curl https://your-app.fly.dev/
# Check OAuth discovery
curl https://your-app.fly.dev/.well-known/oauth-authorization-server
# Check MCP endpoint
curl https://your-app.fly.dev/mcp
force_https = true in fly.tomlFly.io pricing optimization tips:
# In fly.toml - auto-stop when idle
[http_service]
auto_stop_machines = 'stop' # Stop when idle
auto_start_machines = true # Start on request
min_machines_running = 0 # No always-on instances
Expected costs:
The fastest way to see the complete workflow (examples/hello_preview.py):
import asyncio
from chuk_mcp_linkedin.posts import ComposablePost
from chuk_mcp_linkedin.themes import ThemeManager
from chuk_mcp_linkedin.manager_factory import ManagerFactory, set_factory
async def main():
# Initialize factory with memory-based artifacts
factory = ManagerFactory(use_artifacts=True, artifact_provider="memory")
set_factory(factory)
mgr = factory.get_manager("demo_user")
# Step 1: Compose a post
theme = ThemeManager().get_theme("thought_leader")
post = ComposablePost("text", theme=theme)
post.add_hook("question", "What's the most underrated growth lever on LinkedIn in 2025?")
post.add_body("Hint: documents. Short, skimmable, 5–10 pages. Try it this week.", structure="linear")
post.add_cta("curiosity", "Tried docs vs text lately?")
post.add_hashtags(["LinkedInTips", "B2B", "ContentStrategy"])
text = post.compose()
# Step 2: Create a draft
draft = mgr.create_draft("Hello Preview Demo", "text")
mgr.update_draft(draft.draft_id, content={"text": text})
# Step 3: Generate preview URL
preview_url = await mgr.generate_preview_url(
draft_id=draft.draft_id,
base_url="http://localhost:8000",
expires_in=3600
)
print(f"Preview URL: {preview_url}")
if __name__ == "__main__":
asyncio.run(main())
Run it:
# Run the example
uv run python examples/hello_preview.py
# Start HTTP server to view preview (separate terminal)
OAUTH_ENABLED=false uv run linkedin-mcp http --port 8000
# Open the preview URL in your browser
Output:
🚀 LinkedIn MCP Server - Hello Preview Demo
📝 Step 1: Composing post...
✓ Post composed (193 chars)
📋 Step 2: Creating draft...
✓ Draft created (ID: draft_2_1762129805)
🔗 Step 3: Generating preview URL...
✓ Preview URL generated
Preview URL: http://localhost:8000/preview/04a0c703...
Comprehensive examples in the examples/ directory:
# OAuth flow demonstration (authentication)
python examples/oauth_linkedin_example.py
# Complete component showcase
python examples/showcase_all_components.py
# Charts and data visualization
python examples/demo_charts_preview.py
# Media types showcase
python examples/showcase_media_types.py
See examples/README.md for complete list and OAuth setup instructions.
# Install dependencies
make install
make dev
# Install pre-commit hooks
make hooks-install
# Run all tests
make test
# Run with coverage
make coverage
# Run specific test
uv run pytest tests/test_composition.py -v
# Format code
make format
# Run linter
make lint
# Type checking
make typecheck
# Security check
make security
# All quality checks
make quality
# Run full CI pipeline locally
make ci
# Quick CI check
make ci-quick
# Pre-commit checks
make pre-commit
Based on analysis of 1M+ posts across 9K company pages:
Document Posts (PDF) - 45.85% engagement (HIGHEST)
Poll Posts - 200%+ higher reach (MOST UNDERUSED)
Video Posts - 1.4x engagement (GROWING)
Image Posts - 2x more comments than text
Carousel Posts - Declining format
Built on ChukMCPServer - a modular MCP server framework providing:
chuk-mcp-linkedin/
├── src/chuk_mcp_linkedin/
│ ├── api/ # LinkedIn API client
│ ├── models/ # Data models (Pydantic)
│ ├── posts/ # Post composition
│ │ ├── composition.py # ComposablePost class
│ │ └── components/ # Hook, Body, CTA, Hashtags
│ ├── preview/ # Preview system
│ │ ├── post_preview.py # HTML preview generation
│ │ ├── artifact_preview.py # Artifact storage & URLs
│ │ └── component_renderer.py # Component rendering
│ ├── themes/ # Theme system
│ ├── tokens/ # Design token system
│ ├── tools/ # MCP tools
│ ├── utils/ # Utilities
│ ├── manager.py # Draft & session management
│ ├── cli.py # CLI implementation
│ ├── server.py # MCP server (legacy)
│ └── async_server.py # ChukMCPServer-based async server
├── tests/ # Comprehensive test suite (96% coverage)
├── examples/ # Usage examples
├── docs/ # Documentation
├── .github/workflows/ # CI/CD workflows
├── Dockerfile # Multi-stage Docker build
├── docker-compose.yml # Docker Compose config
├── Makefile # Development automation
└── pyproject.toml # Project configuration
Contributions welcome! Please read CONTRIBUTING.md for guidelines.
git checkout -b feature/amazing-feature)make check)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)# Run all tests
make test
# Run with coverage
make coverage
# Open coverage report
make coverage-html
Apache License 2.0 - see LICENSE for details.
This is a demonstration project provided as-is for learning and testing purposes.
Data Sources:
Inspired by:
For questions and issues, please refer to the project documentation.
See CHANGELOG.md for version history.
Install via CLI
npx mdskills install IBM/chuk-mcp-linkedinLinkedIn MCP Server is a free, open-source AI agent skill. Design system MCP server for creating high-performing LinkedIn content Quick Start • Installation • Documentation • A professional Model Context Protocol (MCP) server for LinkedIn content creation, featuring a shadcn-inspired component system, 10 performance-tuned themes, and data-driven optimization based on 1M+ post analysis. Built on ChukMCPServer — a modular, zero-configuration MCP server fram
Install LinkedIn MCP Server with a single command:
npx mdskills install IBM/chuk-mcp-linkedinThis downloads the skill files into your project and your AI agent picks them up automatically.
LinkedIn MCP Server works with Claude Code, Claude Desktop, Cursor, Vscode Copilot, Windsurf, Continue Dev, Gemini Cli, Amp, Roo Code, Goose. Skills use the open SKILL.md format which is compatible with any AI coding agent that reads markdown instructions.