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
1# LinkedIn MCP Server23<div align="center">45**Design system MCP server for creating high-performing LinkedIn content**67[](https://www.python.org/downloads/)8[](https://opensource.org/licenses/Apache-2.0)9[](https://github.com/psf/black)10[](https://modelcontextprotocol.io)1112[Features](#features) •13[Quick Start](#quick-start) •14[Installation](#installation) •15[Documentation](#documentation) •16[Examples](#examples)1718</div>1920---2122## Overview2324A 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.2526Built on **ChukMCPServer** — a modular, zero-configuration MCP server framework with smart environment detection and production-ready defaults.2728**What it does:**29- ✅ Compose posts with theme-based components and variants30- ✅ Upload documents (PDF/PPTX/DOCX) via LinkedIn API31- ✅ Preview posts with session-isolated artifact storage32- ✅ Publish and schedule posts to LinkedIn33- ✅ Optimize content using 2025 performance data34- ✅ Generate secure, time-limited preview URLs3536**What it doesn't do:**37- ❌ Create PowerPoint/PDF files (this server focuses on LinkedIn content composition and publishing)3839### 🔒 Privacy & Security4041**Token Security:**42- Tokens never logged in plaintext (8-char prefix at DEBUG level only)43- All sensitive data (tokens, codes, user IDs) redacted in logs44- OAuth access tokens: Short-lived (default 15 minutes) to reduce replay risk45- OAuth refresh tokens: Daily rotation for maximum security46- LinkedIn-issued tokens: Stored server-side, refreshed automatically47- No tokens persisted to filesystem (Redis/memory sessions only)4849**Draft Isolation:**50- All drafts scoped to authenticated user's session51- No cross-user access possible (enforced by `@requires_auth` decorator)52- Draft artifacts automatically deleted on session expiration5354**Artifact Storage:**55- **Memory provider**: Artifacts cleared on server restart56- **Redis provider**: TTL-based expiration (default: 1 hour)57- **S3 provider**: Presigned URLs expire after configured time (default: 1 hour)5859**Session Management:**60- Sessions validated on every request61- Automatic cleanup of expired sessions62- CSRF protection enabled on all state-changing operations6364**OAuth 2.1 Compliance (RFC 9728):**65- **Authorization Server Discovery**: [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) metadata at `/.well-known/oauth-authorization-server`66- **Protected Resource Metadata**: [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) at `/.well-known/oauth-protected-resource`67- **JWT Access Tokens**: [RFC 9068](https://datatracker.ietf.org/doc/html/rfc9068) format with short TTL68- **PKCE**: Required for all authorization flows (S256 challenge method)69- **State & Nonce**: Enforced to prevent CSRF and replay attacks7071**OAuth Modes:**72- **LinkedIn Direct Mode** (default): Server handles OAuth flow directly with LinkedIn73- **Keycloak Mode**: Delegates OAuth to Keycloak, which manages LinkedIn as Identity Provider74 - Centralized authentication and SSO support75 - Token brokering via Keycloak's Identity Provider integration76 - Enterprise features: LDAP, SAML, 2FA77 - See [Keycloak Setup Guide](docs/KEYCLOAK_OAUTH_SETUP.md)78- **Passthrough Mode**: Accept LinkedIn bearer tokens directly (no OAuth flow)79 - Ideal for testing and development80 - No OAuth credentials required81 - Token validated with LinkedIn userinfo endpoint82 - See [Passthrough Mode Guide](docs/PASSTHROUGH_MODE.md)8384> **LinkedIn API Compliance**: You are responsible for complying with [LinkedIn's API Terms of Service](https://www.linkedin.com/legal/l/api-terms-of-use) and rate limits. This server does not implement rate limiting—configure your own reverse proxy or API gateway as needed.8586## Features8788### 🎨 Design System Architecture89- **Component-based composition** - Build posts from reusable components (Hook, Body, CTA, Hashtags)90- **CVA-inspired variants** - Type-safe variants with compound variant support91- **10 pre-built themes** - Thought Leader, Data Driven, Storyteller, and more92- **Design tokens** - Centralized styling system for consistency93- **Shadcn philosophy** - Copy, paste, and own your components9495### 📊 Data-Driven Optimization96Based on 2025 analysis of 1M+ posts across 9K company pages:97- **Document posts**: 45.85% median engagement (highest in dataset)98- **Poll posts**: 200%+ higher median reach (most underused format)99- **Video posts**: 1.4x median engagement, 69% YoY growth100- **Optimal timing**: Tuesday-Thursday, 7-9 AM (peak engagement window)101- **First 210 chars**: Critical hook window before LinkedIn's "see more" truncation102103<details>104<summary><strong>Data & Methodology</strong></summary>105106**Dataset**: 1,042,183 posts from 9,247 company pages (Jan–Dec 2025)107108**Metrics**:109- *Engagement* = (likes + comments + shares) / impressions110- *Reach* = unique viewers per post111- *Growth* = year-over-year change in engagement rate112113**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.114115**Limitations**: Dataset skews toward B2B tech companies (63% of sample). Results may vary for consumer brands or regional markets.116117</details>118119### 🖥️ Preview & Artifact System120- **Pixel-perfect LinkedIn UI** - Authentic post card rendering121- **Real-time analytics** - Character counts, engagement predictions122- **Document rendering** - PDF/PPTX pages as images (like LinkedIn)123- **Session isolation** - Secure, session-based draft storage124- **Artifact storage** - Multiple backends (memory, S3, IBM COS)125- **Presigned URLs** - Time-limited, secure preview URLs126127### 🚀 Professional CLI128- **Built on ChukMCPServer**: Modular framework with zero-config deployment129- **Multiple modes**: STDIO (Claude Desktop), HTTP (API), Auto-detect130- **Smart environment detection**: Auto-configures for local dev, Docker, Fly.io, etc.131- **Debug logging**: Built-in logging and error handling132- **Docker support**: Multi-stage builds, security hardened133- **Entry points**: `linkedin-mcp` and `linkedin-mcp-server` commands134135### 🔧 Developer Experience136- **96% test coverage** - 1058 tests passing137- **CI/CD ready** - GitHub Actions, pre-commit hooks138- **Type-safe** - Full MyPy type annotations139- **Well-documented** - Extensive docs and examples140141## Quick Start142143### Option 1: Use the Public MCP Server (Recommended)144145The easiest way to get started is to use our hosted MCP server at `https://linkedin.chukai.io`.146147> **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](#deployment)).148149**Add to Claude Desktop:**1501511. Open your Claude Desktop configuration file:152 - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`153 - **Windows**: `C:\Users\<YourUsername>\AppData\Roaming\Claude\claude_desktop_config.json`154155 (Replace `<YourUsername>` with your actual Windows username)1561572. Add the LinkedIn MCP server (no trailing slash):158159```json160{161 "mcpServers": {162 "linkedin": {163 "url": "https://linkedin.chukai.io"164 }165 }166}167```1681693. Restart Claude Desktop1701714. Authenticate with LinkedIn when prompted (you'll be redirected to LinkedIn OAuth)172173**Use with MCP CLI:**174175```bash176# Install MCP CLI (using uvx - no separate install needed)177# Requires: ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable178179# Connect with Claude180uvx mcp-cli --server https://linkedin.chukai.io --provider anthropic --model claude-sonnet-4-5181182# Or with OpenAI183uvx mcp-cli --server https://linkedin.chukai.io --provider openai --model gpt-5-mini184185# Or use local Ollama (no API key needed)186uvx mcp-cli --server https://linkedin.chukai.io187```188189The public server includes:190- ✅ **OAuth 2.1 compliance** with full RFC support:191 - Authorization Server Discovery ([RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)) at `/.well-known/oauth-authorization-server`192 - Protected Resource Metadata ([RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)) at `/.well-known/oauth-protected-resource`193 - JWT Access Tokens ([RFC 9068](https://datatracker.ietf.org/doc/html/rfc9068))194- ✅ **Redis session storage** for multi-instance reliability195- ✅ **S3-compatible artifact storage** (Tigris) with presigned URLs196- ✅ **Automatic scaling** and high availability (Fly.io)197- ✅ **Secure preview URLs** with configurable expiration (default: 1 hour)198199### Option 2: Run Locally200201Want to run your own instance? Install and run the server locally:202203**1. Install the Package**204205```bash206# Basic installation207pip install chuk-mcp-linkedin208209# With HTTP server support210pip install chuk-mcp-linkedin[http]211212# With document preview support213pip install chuk-mcp-linkedin[preview]214215# For development216pip install chuk-mcp-linkedin[dev]217```218219**2. Set Up Environment Variables**220221Create a `.env` file:222223```bash224# LinkedIn OAuth credentials (required)225LINKEDIN_CLIENT_ID=your_client_id226LINKEDIN_CLIENT_SECRET=your_client_secret227LINKEDIN_REDIRECT_URI=http://localhost:8000/oauth/callback228229# Optional: OAuth server URL (for discovery endpoint)230OAUTH_SERVER_URL=http://localhost:8000231232# Session storage (default: memory)233SESSION_PROVIDER=memory234235# Enable publishing (default: false)236ENABLE_PUBLISHING=true237```238239**3. Run the Server**240241```bash242# STDIO mode (for Claude Desktop)243linkedin-mcp stdio244245# HTTP mode (API server)246linkedin-mcp http --port 8000247248# Auto-detect mode249linkedin-mcp auto250251# With debug logging252linkedin-mcp stdio --debug253```254255**4. Configure Claude Desktop (Local Server)**256257```json258{259 "mcpServers": {260 "linkedin": {261 "command": "linkedin-mcp",262 "args": ["stdio"],263 "env": {264 "LINKEDIN_CLIENT_ID": "your_client_id",265 "LINKEDIN_CLIENT_SECRET": "your_client_secret"266 }267 }268 }269}270```271272### Create Your First Post273274```python275from chuk_mcp_linkedin.posts import ComposablePost276from chuk_mcp_linkedin.themes import ThemeManager277278# Get a theme279theme = ThemeManager().get_theme("thought_leader")280281# Compose a post282post = ComposablePost("text", theme=theme)283post.add_hook("stat", "95% of LinkedIn posts get zero comments")284post.add_body("""285Here's why (and how to fix it):286287Most posts lack these 3 elements:288289→ Strong hook (first 210 characters)290→ Clear value (what's in it for them)291→ Conversation starter (invite engagement)292293Start treating posts like conversations, not broadcasts.294""", structure="listicle")295post.add_cta("curiosity", "What's your biggest LinkedIn frustration?")296post.add_hashtags(["LinkedInTips", "ContentStrategy"])297298# Get the composed text299text = post.compose()300print(text)301```302303## Installation304305### Prerequisites306307- Python 3.11 or higher308- LinkedIn OAuth credentials ([create an app](https://www.linkedin.com/developers/))309310### Installation Options311312```bash313# Basic installation (STDIO mode only)314pip install chuk-mcp-linkedin315316# Recommended: with uv (faster, more reliable)317uv pip install chuk-mcp-linkedin318```319320### Optional Extras321322Install additional features as needed:323324| Extra | Command | Includes | Use Case |325|-------|---------|----------|----------|326| **http** | `pip install chuk-mcp-linkedin[http]` | uvicorn, starlette | Run as HTTP API server |327| **preview** | `pip install chuk-mcp-linkedin[preview]` | pdf2image, Pillow, python-pptx, python-docx, PyPDF2 | Document preview rendering |328| **dev** | `pip install chuk-mcp-linkedin[dev]` | pytest, black, ruff, mypy, pre-commit | Development & testing |329| **all** | `pip install "chuk-mcp-linkedin[dev,http,preview]"` | All of the above | Full installation |330331**System Dependencies (Preview Support):**332333```bash334# macOS335brew install poppler336337# Ubuntu/Debian338sudo apt-get install poppler-utils339340# Windows (using Chocolatey)341choco install poppler342```343344### From Source345346```bash347# Clone from your source repository348uv pip install -e ".[dev,http,preview]"349```350351## Usage352353### CLI Commands354355```bash356# Get help357linkedin-mcp --help358359# STDIO mode (for Claude Desktop)360linkedin-mcp stdio361362# HTTP mode (API server on port 8000)363linkedin-mcp http --host 0.0.0.0 --port 8000364365# Auto-detect best mode366linkedin-mcp auto367368# Enable debug logging369linkedin-mcp stdio --debug --log-level DEBUG370```371372### Python API373374#### Simple Text Post375376```python377from chuk_mcp_linkedin.posts import ComposablePost378from chuk_mcp_linkedin.themes import ThemeManager379380# Get theme381theme_mgr = ThemeManager()382theme = theme_mgr.get_theme("thought_leader")383384# Create post385post = ComposablePost("text", theme=theme)386post.add_hook("question", "What drives innovation in 2025?")387post.add_body("Innovation comes from diverse perspectives...", structure="linear")388post.add_cta("direct", "Share your thoughts!")389390# Compose final text391final_text = post.compose()392```393394#### Document Post (Highest Engagement)395396Document posts have 45.85% engagement rate - the highest format in 2025!397398```python399from chuk_mcp_linkedin.posts import ComposablePost400401# Compose post text (publishing via MCP server with OAuth)402post = ComposablePost("document", theme=theme)403post.add_hook("stat", "Document posts get 45.85% engagement")404post.add_body("Our Q4 results are in. Here's what we learned 📊")405post.add_cta("curiosity", "What's your biggest takeaway?")406text = post.compose()407408# Publishing is done via MCP server tools with OAuth authentication409# See examples/oauth_linkedin_example.py for OAuth flow410# See docs/OAUTH.md for setup instructions411```412413#### Poll Post (Highest Reach)414415Polls get 200%+ higher reach than average posts!416417```python418# Create poll419post = ComposablePost("poll", theme=theme)420post.add_hook("question", "Quick question for my network:")421post.add_body("What's your biggest LinkedIn challenge in 2025?")422423# Note: Actual poll creation uses LinkedIn API424# This creates the post text; poll options go via API425```426427### Preview System428429Preview your posts before publishing with automatic URL detection:430431```python432from chuk_mcp_linkedin.manager import LinkedInManager433434manager = LinkedInManager()435436# Create draft437draft = manager.create_draft("My Post", "text")438# ... compose post ...439440# Generate HTML preview (auto-opens in browser)441preview_path = manager.generate_html_preview(draft.draft_id)442```443444**MCP Tool: linkedin_preview_url**445446Generate shareable preview URLs with automatic server detection:447448```python449# Via MCP tool450{451 "tool": "linkedin_preview_url",452 "arguments": {453 "draft_id": "draft_123" # Optional, uses current draft if not provided454 }455}456```457458**Preview URL Behavior:**459- **Production (OAuth)**: Automatically uses deployed server URL from `OAUTH_SERVER_URL` env var460 - Example: `https://linkedin.chukai.io/preview/abc123`461- **Local Development**: Defaults to `http://localhost:8000/preview/abc123`462- **Manual Override**: Can specify custom `base_url` parameter if needed463464**Environment Variables:**465```bash466# Production - preview URLs use this automatically467export OAUTH_SERVER_URL=https://linkedin.chukai.io468469# Local - no configuration needed (defaults to localhost:8000)470```471472**CLI Preview (Legacy):**473```bash474# Preview current draft475python preview_post.py476477# Preview specific draft478python preview_post.py draft_id_here479480# List all drafts481python preview_post.py --list482```483484### Session Management & Artifact Storage485486The server includes enterprise-grade session management and artifact storage powered by `chuk-artifacts`:487488**Features:**489- 🔒 **Session isolation** - Each session only sees their own drafts490- 📦 **Artifact storage** - Secure, session-based storage with grid architecture491- 🔗 **Presigned URLs** - Time-limited, secure preview URLs492- ☁️ **Multiple backends** - Memory, filesystem, S3, IBM Cloud Object Storage493- 🧹 **Auto cleanup** - Automatic expiration of old previews494495#### Session-Based Drafts496497```python498from chuk_mcp_linkedin.manager import LinkedInManager499500# Create manager with session ID501manager = LinkedInManager(502 session_id="user_alice",503 use_artifacts=True,504 artifact_provider="memory" # or "filesystem", "s3", "ibm-cos"505)506507# Drafts are automatically locked to this session508draft = manager.create_draft("My Post", "text")509510# Only this session can access the draft511accessible = manager.is_draft_accessible(draft.draft_id) # True for "user_alice"512513# Different session cannot access514other_manager = LinkedInManager(session_id="user_bob")515accessible = other_manager.is_draft_accessible(draft.draft_id) # False516```517518#### Artifact-Based Previews519520Generate secure preview URLs with automatic expiration:521522```python523from chuk_mcp_linkedin.preview import get_artifact_manager524525# Initialize artifact manager526async with await get_artifact_manager(provider="memory") as artifacts:527 # Create session528 session_id = artifacts.create_session(user_id="alice")529530 # Store preview531 artifact_id = await artifacts.store_preview(532 html_content="<html>...</html>",533 draft_id="draft_123",534 draft_name="My Post",535 session_id=session_id536 )537538 # Generate presigned URL (expires in 1 hour)539 url = await artifacts.get_preview_url(540 artifact_id=artifact_id,541 session_id=session_id,542 expires_in=3600543 )544545 print(f"Preview URL: {url}")546```547548#### MCP Tool: linkedin_preview_url549550The `linkedin_preview_url` tool generates session-isolated preview URLs:551552```json553{554 "tool": "linkedin_preview_url",555 "arguments": {556 "draft_id": "draft_123", // optional: defaults to current draft557 "base_url": "https://linkedin.chukai.io", // optional: auto-detected from OAUTH_SERVER_URL558 "expires_in": 3600 // optional: default 3600s559 }560}561```562563**Response:**564```json565{566 "url": "https://linkedin.chukai.io/preview/04a0c703d98d428fae0e550c885523f7",567 "draft_id": "draft_123",568 "artifact_id": "04a0c703d98d428fae0e550c885523f7",569 "expires_in": 3600570}571```572573The URL is shareable and does not require authentication. It will expire automatically after the specified time.574575#### Storage Providers576577Configure storage backend based on your needs:578579**Memory (Default):**580```python581# Fast, ephemeral storage for development582manager = LinkedInManager(use_artifacts=True, artifact_provider="memory")583```584585**Filesystem:**586```python587# Persistent storage on disk588manager = LinkedInManager(use_artifacts=True, artifact_provider="filesystem")589# Stores in: .artifacts/linkedin-drafts/590```591592**S3:**593```bash594# Configure via environment variables595export ARTIFACT_PROVIDER=s3596export ARTIFACT_S3_BUCKET=my-linkedin-artifacts597export ARTIFACT_S3_REGION=us-east-1598export AWS_ACCESS_KEY_ID=your_key599export AWS_SECRET_ACCESS_KEY=your_secret600```601602```python603from chuk_artifacts.config import configure_s3604605# Or configure programmatically606configure_s3(607 bucket="my-linkedin-artifacts",608 region="us-east-1",609 access_key="your_key",610 secret_key="your_secret"611)612613manager = LinkedInManager(use_artifacts=True, artifact_provider="s3")614```615616**IBM Cloud Object Storage:**617```python618from chuk_artifacts.config import configure_ibm_cos619620configure_ibm_cos(621 bucket="my-linkedin-artifacts",622 endpoint="https://s3.us-south.cloud-object-storage.appdomain.cloud",623 access_key="your_key",624 secret_key="your_secret"625)626```627628#### Grid Architecture629630Artifacts use a hierarchical grid structure:631632```633grid/634├── {sandbox_id}/ # "linkedin-mcp"635│ ├── {session_id}/ # "user_alice"636│ │ ├── {artifact_id}/ # "abc123"637│ │ │ ├── metadata.json638│ │ │ └── content639│ │ └── {artifact_id}/640│ └── {session_id}/641└── {sandbox_id}/642```643644This ensures:645- ✅ Session isolation (users can't access each other's artifacts)646- ✅ Multi-tenant support (different sandboxes)647- ✅ Scalable storage (efficient organization)648- ✅ Easy cleanup (delete by session or sandbox)649650#### Local Development651652For local development without cloud storage:653654```python655# Use in-memory artifact storage656from chuk_mcp_linkedin.manager import LinkedInManager657658manager = LinkedInManager(659 use_artifacts=True,660 artifact_provider="memory" # Fast, ephemeral storage661)662663# Or use filesystem for persistent local storage664manager = LinkedInManager(665 use_artifacts=True,666 artifact_provider="filesystem" # Stores in .artifacts/667)668```669670### Available Themes67167210 pre-built themes for different LinkedIn personas:673674| Theme | Description | Use Case |675|-------|-------------|----------|676| `thought_leader` | Authority and expertise | Industry insights, frameworks |677| `data_driven` | Let numbers tell story | Analytics, research, reports |678| `storyteller` | Narrative-driven | Personal experiences, case studies |679| `community_builder` | Foster conversation | Polls, questions, engagement |680| `technical_expert` | Deep technical knowledge | Engineering, dev, technical topics |681| `personal_brand` | Authentic connection | Behind-the-scenes, personal stories |682| `corporate_professional` | Polished corporate | Official announcements, updates |683| `contrarian_voice` | Challenge status quo | Controversial takes, debate |684| `coach_mentor` | Guide and support | Tips, advice, mentorship |685| `entertainer` | Make LinkedIn fun | Humor, memes, light content |686687### MCP Server Integration688689#### With OAuth (Recommended)690691For HTTP mode with OAuth authentication:692693```json694{695 "mcpServers": {696 "linkedin": {697 "command": "linkedin-mcp",698 "args": ["http", "--port", "8000"],699 "env": {700 "SESSION_PROVIDER": "memory",701 "LINKEDIN_CLIENT_ID": "your_linkedin_client_id",702 "LINKEDIN_CLIENT_SECRET": "your_linkedin_client_secret",703 "OAUTH_ENABLED": "true"704 }705 }706 }707}708```709710Then use with MCP-CLI:711```bash712uvx mcp-cli --server linkedin --provider openai --model gpt-5-mini713```714715See [docs/OAUTH.md](docs/OAUTH.md) for complete OAuth setup instructions.716717#### STDIO Mode (Desktop Clients)718719For Claude Desktop and other desktop client integration:720721```json722{723 "mcpServers": {724 "linkedin": {725 "command": "linkedin-mcp",726 "args": ["stdio"]727 }728 }729}730```731732**Note**: OAuth is required for publishing tools. STDIO mode supports all other tools (drafting, composition, previews).733734## Docker735736### Quick Start737738```bash739# Build image740docker build -t chuk-mcp-linkedin:latest .741742# Run in STDIO mode743docker-compose --profile stdio up -d744745# Run in HTTP mode746docker-compose --profile http up -d747748# View logs749docker-compose logs -f750```751752### Makefile Commands753754```bash755make docker-build # Build Docker image756make docker-run-stdio # Run in STDIO mode757make docker-run-http # Run in HTTP mode on port 8000758make docker-test # Build and test image759make docker-logs # View container logs760make docker-stop # Stop containers761make docker-clean # Clean up Docker resources762```763764### Environment Variables765766Create a `.env` file:767768```env769# ============================================================================770# OAuth Configuration (Required for Publishing)771# ============================================================================772773# LinkedIn OAuth Credentials (from https://www.linkedin.com/developers/apps)774LINKEDIN_CLIENT_ID=your_linkedin_client_id775LINKEDIN_CLIENT_SECRET=your_linkedin_client_secret776777# OAuth Server URLs778LINKEDIN_REDIRECT_URI=http://localhost:8000/oauth/callback # Must match LinkedIn app settings779OAUTH_SERVER_URL=http://localhost:8000780OAUTH_ENABLED=true781782# Session Storage (for OAuth tokens)783SESSION_PROVIDER=memory # Development: memory | Production: redis784SESSION_REDIS_URL=redis://localhost:6379/0 # Required if SESSION_PROVIDER=redis785786# ============================================================================787# OAuth Token TTL Configuration (Optional - Defaults Shown)788# ============================================================================789790# Authorization codes - Temporary codes exchanged for access tokens during OAuth flow791# Short-lived for security (5 minutes)792OAUTH_AUTH_CODE_TTL=300793794# Access tokens - Used by MCP clients to authenticate API requests795# Should be short-lived and refreshed regularly (15 minutes)796OAUTH_ACCESS_TOKEN_TTL=900797798# Refresh tokens - Long-lived tokens that obtain new access tokens without re-authentication799# Short lifetime requires daily re-authorization for maximum security (1 day)800OAUTH_REFRESH_TOKEN_TTL=86400801802# Client registrations - How long dynamically registered MCP clients remain valid (1 year)803OAUTH_CLIENT_REGISTRATION_TTL=31536000804805# LinkedIn tokens - Access and refresh tokens from LinkedIn stored server-side806# Auto-refreshed when expired (1 day, more secure than LinkedIn's 60-day default)807OAUTH_EXTERNAL_TOKEN_TTL=86400808809# ============================================================================810# Server Configuration811# ============================================================================812DEBUG=0813HTTP_PORT=8000814815# LinkedIn Person URN (for API calls - auto-detected from OAuth token)816LINKEDIN_PERSON_URN=urn:li:person:YOUR_ID # Optional: Auto-fetched via OAuth817```818819**Key Points:**820- **SESSION_PROVIDER=memory** - Required for development (no Redis needed)821- **SESSION_PROVIDER=redis** - Required for production (with SESSION_REDIS_URL)822- **OAuth is required** - Publishing tools (`linkedin_publish`) require OAuth authentication823- **Token TTLs** - Defaults are security-focused (short lifetimes, daily re-auth)824825See [docs/OAUTH.md](docs/OAUTH.md) for complete OAuth setup and [docs/DOCKER.md](docs/DOCKER.md) for Docker deployment.826827## Production Deployment828829### Fly.io Deployment (Recommended)830831Deploy the LinkedIn MCP server to Fly.io with Redis session storage:832833#### Prerequisites8348351. **Fly.io Account** - [Sign up at fly.io](https://fly.io/app/sign-up)8362. **Fly CLI** - Install: `curl -L https://fly.io/install.sh | sh`8373. **LinkedIn OAuth App** - Create at [LinkedIn Developers](https://www.linkedin.com/developers/apps)8384. **Redis Instance** - Create on Fly.io (or use Upstash)839840#### Step 1: Create Fly.io App841842```bash843# Login to Fly.io844fly auth login845846# Create app (generates fly.toml)847fly launch --no-deploy848849# Choose app name (e.g., your-linkedin-mcp)850# Choose region (e.g., cdg for Paris)851```852853#### Step 2: Create Redis Instance854855```bash856# Create Redis on Fly.io857fly redis create858859# Note the Redis URL from output:860# redis://default:PASSWORD@fly-INSTANCE-NAME.upstash.io:6379861```862863#### Step 3: Create Tigris Storage Bucket864865```bash866# Create Tigris S3-compatible storage for preview artifacts867fly storage create --name your-linkedin-mcp868869# Fly automatically sets these secrets on your app:870# - AWS_ACCESS_KEY_ID871# - AWS_SECRET_ACCESS_KEY872# - AWS_ENDPOINT_URL_S3873# - AWS_REGION874# - BUCKET_NAME875```876877#### Step 4: Configure Environment Variables878879**Required Secrets Reference:**880881| Secret | Required | Source | Purpose |882|--------|----------|--------|---------|883| `LINKEDIN_CLIENT_ID` | ✅ Yes | [LinkedIn Developers Portal](https://www.linkedin.com/developers/apps) | OAuth client ID |884| `LINKEDIN_CLIENT_SECRET` | ✅ Yes | LinkedIn Developers Portal | OAuth client secret |885| `SESSION_REDIS_URL` | ✅ Yes | Output from `fly redis create` (Step 2) | Redis connection string for sessions |886| `SESSION_PROVIDER` | ✅ Yes | Set to `redis` | Enable Redis session backend |887| `OAUTH_SERVER_URL` | ✅ Yes | Your Fly.io app URL | OAuth discovery base URL |888| `LINKEDIN_REDIRECT_URI` | ✅ Yes | `{OAUTH_SERVER_URL}/oauth/callback` | OAuth callback endpoint |889| `AWS_ACCESS_KEY_ID` | Auto | `fly storage create` (Step 3) | Tigris S3 access key (auto-set) |890| `AWS_SECRET_ACCESS_KEY` | Auto | `fly storage create` (Step 3) | Tigris S3 secret (auto-set) |891| `AWS_ENDPOINT_URL_S3` | Auto | `fly storage create` (Step 3) | Tigris S3 endpoint (auto-set) |892| `AWS_REGION` | Auto | `fly storage create` (Step 3) | Tigris S3 region (auto-set) |893894**Set required secrets with Fly CLI:**895896```bash897# LinkedIn OAuth credentials (from https://www.linkedin.com/developers/apps)898fly secrets set \899 LINKEDIN_CLIENT_ID=your_linkedin_client_id \900 LINKEDIN_CLIENT_SECRET=your_linkedin_client_secret \901 --app your-linkedin-mcp902903# Redis connection (from step 2)904fly secrets set \905 SESSION_REDIS_URL="redis://default:PASSWORD@fly-INSTANCE-NAME.upstash.io:6379" \906 SESSION_PROVIDER=redis \907 --app your-linkedin-mcp908909# OAuth server configuration910fly secrets set \911 OAUTH_SERVER_URL=https://your-linkedin-mcp.fly.dev \912 LINKEDIN_REDIRECT_URI=https://your-linkedin-mcp.fly.dev/oauth/callback \913 --app your-linkedin-mcp914```915916> **Note**: AWS credentials for Tigris (Step 3) are automatically set when you run `fly storage create`. No manual configuration needed!917918#### Step 5: Configure fly.toml919920Update `fly.toml` with production settings:921922```toml923app = 'your-linkedin-mcp'924primary_region = 'cdg'925926[build]927928[http_service]929 internal_port = 8000930 force_https = true931 auto_stop_machines = 'stop'932 auto_start_machines = true933 min_machines_running = 0934 processes = ['app']935936[[vm]]937 memory = '1gb'938 cpu_kind = 'shared'939 cpus = 1940941[env]942 SESSION_PROVIDER = 'redis'943 ENABLE_PUBLISHING = true944 OAUTH_SERVER_URL = 'https://your-linkedin-mcp.fly.dev'945 LINKEDIN_REDIRECT_URI = 'https://your-linkedin-mcp.fly.dev/oauth/callback'946947 # Artifact Storage (Tigris S3-compatible)948 ARTIFACT_PROVIDER = 's3'949 ARTIFACT_S3_BUCKET = 'your-linkedin-mcp'950 # AWS_* secrets automatically set by `fly storage create`951```952953#### Step 6: Deploy954955```bash956# Deploy to Fly.io957fly deploy958959# Check deployment status960fly status961962# View logs963fly logs964965# Test OAuth endpoint966curl https://your-linkedin-mcp.fly.dev/.well-known/oauth-authorization-server967```968969#### Step 7: Configure MCP Client970971Update your MCP client configuration (e.g., `~/.mcp-cli/servers.yaml`):972973```yaml974servers:975 linkedin:976 url: https://your-linkedin-mcp.fly.dev # No trailing slash!977 oauth: true978```979980Test the connection:981982```bash983uvx mcp-cli --server linkedin --provider openai --model gpt-5-mini984```985986### Redis Configuration987988#### Development (Memory)989990For local development, use in-memory session storage:991992```bash993# .env file994SESSION_PROVIDER=memory995```996997No Redis installation required. Sessions are lost when the server restarts.998999#### Production (Redis)10001001For production, use Redis for persistent session storage:10021003**Option 1: Fly.io Redis (Upstash)**10041005```bash1006# Create Redis instance1007fly redis create10081009# Get connection details1010fly redis status your-redis-instance10111012# Set as secret1013fly secrets set SESSION_REDIS_URL="redis://default:PASSWORD@fly-INSTANCE.upstash.io:6379"1014```10151016**Option 2: External Redis (Upstash, AWS ElastiCache, etc.)**10171018```bash1019# Set Redis URL1020export SESSION_REDIS_URL="redis://username:password@host:port/db"1021export SESSION_PROVIDER=redis1022```10231024**Environment Variables:**10251026```env1027# Session Provider1028SESSION_PROVIDER=redis # Required: redis | memory10291030# Redis Connection (required if SESSION_PROVIDER=redis)1031SESSION_REDIS_URL=redis://default:password@host:637910321033# Optional Redis settings1034REDIS_TLS_INSECURE=0 # Set to 1 to disable TLS cert verification (not recommended)1035```10361037### Custom Domain Setup10381039Configure a custom domain for your deployment:10401041#### Step 1: Add Domain to Fly.io10421043```bash1044# Add custom domain1045fly certs create linkedin.yourdomain.com10461047# Verify DNS settings1048fly certs show linkedin.yourdomain.com1049```10501051#### Step 2: Update DNS10521053Add DNS records (check output from previous command):10541055```1056Type: CNAME1057Name: linkedin.yourdomain.com1058Value: your-linkedin-mcp.fly.dev1059```10601061#### Step 3: Update OAuth URLs10621063```bash1064# Update secrets with custom domain1065fly secrets set \1066 OAUTH_SERVER_URL=https://linkedin.yourdomain.com \1067 LINKEDIN_REDIRECT_URI=https://linkedin.yourdomain.com/oauth/callback1068```10691070#### Step 4: Update LinkedIn App107110721. Go to [LinkedIn Developers](https://www.linkedin.com/developers/apps)10732. Select your app10743. Update "Redirect URLs" to match: `https://linkedin.yourdomain.com/oauth/callback`10751076### Environment Variables Reference10771078Complete list of production environment variables:10791080```env1081# ============================================================================1082# OAuth Configuration (Required for Production)1083# ============================================================================10841085# LinkedIn OAuth Credentials1086LINKEDIN_CLIENT_ID=your_linkedin_client_id1087LINKEDIN_CLIENT_SECRET=your_linkedin_client_secret10881089# OAuth Server URLs (must match LinkedIn app settings)1090# IMPORTANT: This URL is also used for preview URLs (linkedin_preview_url tool)1091OAUTH_SERVER_URL=https://your-app.fly.dev1092LINKEDIN_REDIRECT_URI=https://your-app.fly.dev/oauth/callback1093OAUTH_ENABLED=true10941095# ============================================================================1096# Session Storage (Required for Production)1097# ============================================================================10981099# Production: Use Redis1100SESSION_PROVIDER=redis1101SESSION_REDIS_URL=redis://default:password@fly-instance.upstash.io:637911021103# Development: Use Memory1104# SESSION_PROVIDER=memory11051106# ============================================================================1107# OAuth Token TTL Configuration (Optional - Defaults Shown)1108# ============================================================================11091110OAUTH_AUTH_CODE_TTL=300 # Authorization codes (5 min)1111OAUTH_ACCESS_TOKEN_TTL=900 # Access tokens (15 min)1112OAUTH_REFRESH_TOKEN_TTL=86400 # Refresh tokens (1 day)1113OAUTH_CLIENT_REGISTRATION_TTL=31536000 # Client registrations (1 year)1114OAUTH_EXTERNAL_TOKEN_TTL=86400 # LinkedIn tokens (1 day)11151116# ============================================================================1117# Server Configuration1118# ============================================================================11191120DEBUG=0 # Disable debug mode in production1121HTTP_PORT=8000 # Server port1122ENABLE_PUBLISHING=true # Enable publishing tools11231124# LinkedIn Person URN (optional - auto-detected via OAuth)1125LINKEDIN_PERSON_URN=urn:li:person:YOUR_ID1126```11271128### Logging Configuration11291130Control logging levels in production:11311132```env1133# Production logging1134LOG_LEVEL=INFO # INFO for production, DEBUG for troubleshooting1135MCP_LOG_LEVEL=WARNING # MCP protocol logging11361137# Development logging1138LOG_LEVEL=DEBUG1139MCP_LOG_LEVEL=INFO1140```11411142**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.11431144### Monitoring & Troubleshooting11451146```bash1147# View live logs1148fly logs --app your-linkedin-mcp11491150# Check app status1151fly status --app your-linkedin-mcp11521153# Check Redis status1154fly redis status your-redis-instance11551156# Restart app1157fly apps restart your-linkedin-mcp11581159# Scale app1160fly scale count 2 --app your-linkedin-mcp # 2 instances1161fly scale memory 2048 --app your-linkedin-mcp # 2GB memory1162```11631164### Health Checks11651166The server includes health check endpoints:11671168```bash1169# Check server health1170curl https://your-app.fly.dev/11711172# Check OAuth discovery1173curl https://your-app.fly.dev/.well-known/oauth-authorization-server11741175# Check MCP endpoint1176curl https://your-app.fly.dev/mcp1177```11781179### Security Best Practices118011811. **Never commit secrets** - Use Fly secrets, not environment variables in fly.toml11822. **Use HTTPS only** - Set `force_https = true` in fly.toml11833. **Rotate tokens regularly** - LinkedIn tokens are auto-refreshed11844. **Monitor logs** - Check for failed auth attempts11855. **Use custom domain** - Professional appearance, easier to update11866. **Enable auto-scaling** - Handle traffic spikes automatically11877. **Keep dependencies updated** - Regular security updates11881189### Cost Optimization11901191Fly.io pricing optimization tips:11921193```toml1194# In fly.toml - auto-stop when idle1195[http_service]1196 auto_stop_machines = 'stop' # Stop when idle1197 auto_start_machines = true # Start on request1198 min_machines_running = 0 # No always-on instances1199```12001201**Expected costs**:1202- Free tier: 3 shared-cpu VMs with 256MB RAM1203- Redis: ~$2/month for basic Upstash instance1204- Scaling: ~$0.02/hour per VM after free tier12051206## Documentation12071208- **[Getting Started](docs/GETTING_STARTED.md)** - Complete beginner's guide1209- **[OAuth Guide](docs/OAUTH.md)** - OAuth 2.1 setup and configuration1210- **[API Reference](docs/API.md)** - Full API documentation1211- **[Themes Guide](docs/THEMES.md)** - All themes and customization1212- **[Design Tokens](docs/TOKENS.md)** - Token system reference1213- **[Docker Guide](docs/DOCKER.md)** - Docker deployment1214- **[CI/CD Guide](docs/CI_CD.md)** - Continuous integration1215- **[Development Guide](docs/DEVELOPMENT.md)** - Contributing and development1216- **[Architecture](docs/ARCHITECTURE.md)** - System architecture12171218## Examples12191220### Hello World: Compose → Draft → Preview URL12211222The fastest way to see the complete workflow (`examples/hello_preview.py`):12231224```python1225import asyncio1226from chuk_mcp_linkedin.posts import ComposablePost1227from chuk_mcp_linkedin.themes import ThemeManager1228from chuk_mcp_linkedin.manager_factory import ManagerFactory, set_factory12291230async def main():1231 # Initialize factory with memory-based artifacts1232 factory = ManagerFactory(use_artifacts=True, artifact_provider="memory")1233 set_factory(factory)1234 mgr = factory.get_manager("demo_user")12351236 # Step 1: Compose a post1237 theme = ThemeManager().get_theme("thought_leader")1238 post = ComposablePost("text", theme=theme)1239 post.add_hook("question", "What's the most underrated growth lever on LinkedIn in 2025?")1240 post.add_body("Hint: documents. Short, skimmable, 5–10 pages. Try it this week.", structure="linear")1241 post.add_cta("curiosity", "Tried docs vs text lately?")1242 post.add_hashtags(["LinkedInTips", "B2B", "ContentStrategy"])1243 text = post.compose()12441245 # Step 2: Create a draft1246 draft = mgr.create_draft("Hello Preview Demo", "text")1247 mgr.update_draft(draft.draft_id, content={"text": text})12481249 # Step 3: Generate preview URL1250 preview_url = await mgr.generate_preview_url(1251 draft_id=draft.draft_id,1252 base_url="http://localhost:8000",1253 expires_in=36001254 )1255 print(f"Preview URL: {preview_url}")12561257if __name__ == "__main__":1258 asyncio.run(main())1259```12601261**Run it:**12621263```bash1264# Run the example1265uv run python examples/hello_preview.py12661267# Start HTTP server to view preview (separate terminal)1268OAUTH_ENABLED=false uv run linkedin-mcp http --port 800012691270# Open the preview URL in your browser1271```12721273**Output:**1274```1275🚀 LinkedIn MCP Server - Hello Preview Demo12761277📝 Step 1: Composing post...1278✓ Post composed (193 chars)12791280📋 Step 2: Creating draft...1281✓ Draft created (ID: draft_2_1762129805)12821283🔗 Step 3: Generating preview URL...1284✓ Preview URL generated12851286Preview URL: http://localhost:8000/preview/04a0c703...1287```12881289### More Examples12901291Comprehensive examples in the `examples/` directory:12921293```bash1294# OAuth flow demonstration (authentication)1295python examples/oauth_linkedin_example.py12961297# Complete component showcase1298python examples/showcase_all_components.py12991300# Charts and data visualization1301python examples/demo_charts_preview.py13021303# Media types showcase1304python examples/showcase_media_types.py1305```13061307See [examples/README.md](examples/README.md) for complete list and OAuth setup instructions.13081309## Development13101311### Setup13121313```bash1314# Install dependencies1315make install1316make dev13171318# Install pre-commit hooks1319make hooks-install1320```13211322### Run Tests13231324```bash1325# Run all tests1326make test13271328# Run with coverage1329make coverage13301331# Run specific test1332uv run pytest tests/test_composition.py -v1333```13341335### Code Quality13361337```bash1338# Format code1339make format13401341# Run linter1342make lint13431344# Type checking1345make typecheck13461347# Security check1348make security13491350# All quality checks1351make quality1352```13531354### CI/CD13551356```bash1357# Run full CI pipeline locally1358make ci13591360# Quick CI check1361make ci-quick13621363# Pre-commit checks1364make pre-commit1365```13661367## 2025 LinkedIn Performance Data13681369Based on analysis of 1M+ posts across 9K company pages:13701371### Top Performing Formats137213731. **Document Posts (PDF)** - 45.85% engagement (HIGHEST)1374 - Optimal: 5-10 pages1375 - Format: 1920x1920 square1376 - Min font: 18pt for mobile137713782. **Poll Posts** - 200%+ higher reach (MOST UNDERUSED)1379 - Opportunity: Least used format1380 - Engagement: 3x average reach1381 - Duration: 3-7 days optimal138213833. **Video Posts** - 1.4x engagement (GROWING)1384 - Usage up 69% from 20241385 - Vertical format preferred1386 - Keep under 3 minutes138713884. **Image Posts** - 2x more comments than text1389 - Square format (1080x1080) performs best1390 - Infographics and data viz trending139113925. **Carousel Posts** - Declining format1393 - Down 18% reach, 25% engagement vs 20241394 - Keep to 5-10 slides maximum13951396### Optimal Post Structure13971398- **First 210 characters** - Critical hook window1399- **Ideal length**: 300-800 characters1400- **Hashtags**: 3-5 optimal (not 10+)1401- **Line breaks**: Use for scannability1402- **Best times**: Tue-Thu, 7-9 AM / 12-2 PM / 5-6 PM14031404### First Hour Engagement14051406- **Minimum**: 10 engagements (baseline)1407- **Good**: 50 engagements (algorithm boost)1408- **Viral**: 100+ engagements (maximum reach)14091410## Architecture14111412Built on **ChukMCPServer** - a modular MCP server framework providing:1413- **Zero-config deployment**: Smart environment detection (local, Docker, Fly.io)1414- **Production-ready defaults**: Optimized workers, connection pooling, logging1415- **OAuth 2.1 built-in**: Discovery endpoints, token management, session handling1416- **Multiple transports**: STDIO for desktop clients, HTTP/SSE for API access14171418```1419chuk-mcp-linkedin/1420├── src/chuk_mcp_linkedin/1421│ ├── api/ # LinkedIn API client1422│ ├── models/ # Data models (Pydantic)1423│ ├── posts/ # Post composition1424│ │ ├── composition.py # ComposablePost class1425│ │ └── components/ # Hook, Body, CTA, Hashtags1426│ ├── preview/ # Preview system1427│ │ ├── post_preview.py # HTML preview generation1428│ │ ├── artifact_preview.py # Artifact storage & URLs1429│ │ └── component_renderer.py # Component rendering1430│ ├── themes/ # Theme system1431│ ├── tokens/ # Design token system1432│ ├── tools/ # MCP tools1433│ ├── utils/ # Utilities1434│ ├── manager.py # Draft & session management1435│ ├── cli.py # CLI implementation1436│ ├── server.py # MCP server (legacy)1437│ └── async_server.py # ChukMCPServer-based async server1438├── tests/ # Comprehensive test suite (96% coverage)1439├── examples/ # Usage examples1440├── docs/ # Documentation1441├── .github/workflows/ # CI/CD workflows1442├── Dockerfile # Multi-stage Docker build1443├── docker-compose.yml # Docker Compose config1444├── Makefile # Development automation1445└── pyproject.toml # Project configuration1446```14471448## Contributing14491450Contributions welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.14511452### Development Workflow145314541. Fork the repository14552. Create feature branch (`git checkout -b feature/amazing-feature`)14563. Make changes and add tests14574. Run quality checks (`make check`)14585. Commit changes (`git commit -m 'Add amazing feature'`)14596. Push to branch (`git push origin feature/amazing-feature`)14607. Open Pull Request14611462## Testing14631464- **96% test coverage** - 1058 tests passing1465- **Multiple test types** - Unit, integration, component tests1466- **Artifact system tests** - Session isolation, preview URLs1467- **CI/CD** - GitHub Actions on every push1468- **Pre-commit hooks** - Automatic quality checks14691470```bash1471# Run all tests1472make test14731474# Run with coverage1475make coverage14761477# Open coverage report1478make coverage-html1479```14801481## License14821483Apache License 2.0 - see [LICENSE](LICENSE) for details.14841485> This is a demonstration project provided as-is for learning and testing purposes.14861487## Credits14881489**Data Sources:**1490- 2025 LinkedIn performance data from analysis of 1M+ posts1491- 9K company page benchmarks1492- LinkedIn API documentation14931494**Inspired by:**1495- [shadcn/ui](https://ui.shadcn.com/) - Component philosophy1496- [CVA](https://cva.style/) - Variant system1497- [Model Context Protocol](https://modelcontextprotocol.io) - MCP standard14981499## Support15001501For questions and issues, please refer to the project documentation.15021503## Roadmap15041505- [ ] Additional post types (events, newsletters)1506- [ ] LinkedIn analytics integration1507- [ ] A/B testing framework1508- [ ] Multi-account support1509- [ ] Scheduling and automation1510- [ ] Enhanced preview with real API data1511- [ ] Webhook support for notifications15121513## Changelog15141515See [CHANGELOG.md](CHANGELOG.md) for version history.15161517---15181519<div align="center">15201521**[⬆ back to top](#linkedin-mcp-server)**15221523</div>1524
Full transparency — inspect the skill content before installing.