Track MCP LogoTrack MCP
Track MCP LogoTrack MCP

The world's largest repository of Model Context Protocol servers. Discover, explore, and submit MCP tools.

Product

  • Categories
  • Top MCP
  • New & Updated

Company

  • About

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy

© 2025 TrackMCP. All rights reserved.

Built with ❤️ by Krishna Goyal

    Chuk Mcp Linkedin

    0 stars
    Python
    Updated Oct 18, 2025

    Documentation

    LinkedIn MCP Server

    ---

    Overview

    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:

    • ✅ Compose posts with theme-based components and variants
    • ✅ Upload documents (PDF/PPTX/DOCX) via LinkedIn API
    • ✅ Preview posts with session-isolated artifact storage
    • ✅ Publish and schedule posts to LinkedIn
    • ✅ Optimize content using 2025 performance data
    • ✅ Generate secure, time-limited preview URLs

    What it doesn't do:

    • ❌ Create PowerPoint/PDF files (use [chuk-mcp-pptx](https://github.com/chrishayuk/chuk-mcp-pptx) for that)

    🔒 Privacy & Security

    Token Security:

    • Tokens never logged in plaintext (8-char prefix at DEBUG level only)
    • All sensitive data (tokens, codes, user IDs) redacted in logs
    • OAuth access tokens: Short-lived (default 15 minutes) to reduce replay risk
    • OAuth refresh tokens: Daily rotation for maximum security
    • LinkedIn-issued tokens: Stored server-side, refreshed automatically
    • No tokens persisted to filesystem (Redis/memory sessions only)

    Draft Isolation:

    • All drafts scoped to authenticated user's session
    • No cross-user access possible (enforced by @requires_auth decorator)
    • Draft artifacts automatically deleted on session expiration

    Artifact Storage:

    • Memory provider: Artifacts cleared on server restart
    • Redis provider: TTL-based expiration (default: 1 hour)
    • S3 provider: Presigned URLs expire after configured time (default: 1 hour)

    Session Management:

    • Sessions validated on every request
    • Automatic cleanup of expired sessions
    • CSRF protection enabled on all state-changing operations

    OAuth 2.1 Compliance (RFC 9728):

    • Authorization Server Discovery: RFC 8414 metadata at /.well-known/oauth-authorization-server
    • Protected Resource Metadata: RFC 9728 at /.well-known/oauth-protected-resource
    • JWT Access Tokens: RFC 9068 format with short TTL
    • PKCE: Required for all authorization flows (S256 challenge method)
    • State & Nonce: Enforced to prevent CSRF and replay attacks

    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.

    Features

    🎨 Design System Architecture

    • Component-based composition - Build posts from reusable components (Hook, Body, CTA, Hashtags)
    • CVA-inspired variants - Type-safe variants with compound variant support
    • 10 pre-built themes - Thought Leader, Data Driven, Storyteller, and more
    • Design tokens - Centralized styling system for consistency
    • Shadcn philosophy - Copy, paste, and own your components

    📊 Data-Driven Optimization

    Based on 2025 analysis of 1M+ posts across 9K company pages:

    • Document posts: 45.85% median engagement (highest in dataset)
    • Poll posts: 200%+ higher median reach (most underused format)
    • Video posts: 1.4x median engagement, 69% YoY growth
    • Optimal timing: Tuesday-Thursday, 7-9 AM (peak engagement window)
    • First 210 chars: Critical hook window before LinkedIn's "see more" truncation

    Data & Methodology

    Dataset: 1,042,183 posts from 9,247 company pages (Jan–Dec 2025)

    Metrics:

    • *Engagement* = (likes + comments + shares) / impressions
    • *Reach* = unique viewers per post
    • *Growth* = year-over-year change in engagement rate

    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.

    🖥️ Preview & Artifact System

    • Pixel-perfect LinkedIn UI - Authentic post card rendering
    • Real-time analytics - Character counts, engagement predictions
    • Document rendering - PDF/PPTX pages as images (like LinkedIn)
    • Session isolation - Secure, session-based draft storage
    • Artifact storage - Multiple backends (memory, S3, IBM COS)
    • Presigned URLs - Time-limited, secure preview URLs

    🚀 Professional CLI

    • **Built on ChukMCPServer**: Modular framework with zero-config deployment
    • Multiple modes: STDIO (Claude Desktop), HTTP (API), Auto-detect
    • Smart environment detection: Auto-configures for local dev, Docker, Fly.io, etc.
    • Debug logging: Built-in logging and error handling
    • Docker support: Multi-stage builds, security hardened
    • Entry points: linkedin-mcp and linkedin-mcp-server commands

    🔧 Developer Experience

    • 96% test coverage - 1058 tests passing
    • CI/CD ready - GitHub Actions, pre-commit hooks
    • Type-safe - Full MyPy type annotations
    • Well-documented - Extensive docs and examples

    Quick Start

    Option 1: Use the Public MCP Server (Recommended)

    The 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:

    1. Open your Claude Desktop configuration file:

    • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
    • Windows: C:\Users\\AppData\Roaming\Claude\claude_desktop_config.json

    (Replace `` with your actual Windows username)

    2. Add the LinkedIn MCP server (no trailing slash):

    json
    {
      "mcpServers": {
        "linkedin": {
          "url": "https://linkedin.chukai.io"
        }
      }
    }

    3. Restart Claude Desktop

    4. Authenticate with LinkedIn when prompted (you'll be redirected to LinkedIn OAuth)

    Use with MCP CLI:

    bash
    # 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:

    • ✅ OAuth 2.1 compliance with full RFC support:
    • Authorization Server Discovery (RFC 8414) at /.well-known/oauth-authorization-server
    • Protected Resource Metadata (RFC 9728) at /.well-known/oauth-protected-resource
    • JWT Access Tokens (RFC 9068)
    • ✅ Redis session storage for multi-instance reliability
    • ✅ S3-compatible artifact storage (Tigris) with presigned URLs
    • ✅ Automatic scaling and high availability (Fly.io)
    • ✅ Secure preview URLs with configurable expiration (default: 1 hour)

    Option 2: Run Locally

    Want to run your own instance? Install and run the server locally:

    1. Install the Package

    bash
    # 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:

    bash
    # 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

    bash
    # 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)

    json
    {
      "mcpServers": {
        "linkedin": {
          "command": "linkedin-mcp",
          "args": ["stdio"],
          "env": {
            "LINKEDIN_CLIENT_ID": "your_client_id",
            "LINKEDIN_CLIENT_SECRET": "your_client_secret"
          }
        }
      }
    }

    Create Your First Post

    python
    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)

    Installation

    Prerequisites

    • Python 3.11 or higher
    • LinkedIn OAuth credentials (create an app)

    Installation Options

    bash
    # Basic installation (STDIO mode only)
    pip install chuk-mcp-linkedin
    
    # Recommended: with uv (faster, more reliable)
    uv pip install chuk-mcp-linkedin

    Optional Extras

    Install additional features as needed:

    ExtraCommandIncludesUse Case
    httppip install chuk-mcp-linkedin[http]uvicorn, starletteRun as HTTP API server
    previewpip install chuk-mcp-linkedin[preview]pdf2image, Pillow, python-pptx, python-docx, PyPDF2Document preview rendering
    devpip install chuk-mcp-linkedin[dev]pytest, black, ruff, mypy, pre-commitDevelopment & testing
    allpip install "chuk-mcp-linkedin[dev,http,preview]"All of the aboveFull installation

    System Dependencies (Preview Support):

    bash
    # macOS
    brew install poppler
    
    # Ubuntu/Debian
    sudo apt-get install poppler-utils
    
    # Windows (using Chocolatey)
    choco install poppler

    From Source

    bash
    git clone https://github.com/chrishayuk/chuk-mcp-linkedin.git
    cd chuk-mcp-linkedin
    uv pip install -e ".[dev,http,preview]"

    Usage

    CLI Commands

    bash
    # 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

    Python API

    Simple Text Post

    python
    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 Post (Highest Engagement)

    Document posts have 45.85% engagement rate - the highest format in 2025!

    python
    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

    Poll Post (Highest Reach)

    Polls get 200%+ higher reach than average posts!

    python
    # 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 System

    Preview your posts before publishing with automatic URL detection:

    python
    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:

    python
    # Via MCP tool
    {
        "tool": "linkedin_preview_url",
        "arguments": {
            "draft_id": "draft_123"  # Optional, uses current draft if not provided
        }
    }

    Preview URL Behavior:

    • Production (OAuth): Automatically uses deployed server URL from OAUTH_SERVER_URL env var
    • Example: https://linkedin.chukai.io/preview/abc123
    • Local Development: Defaults to http://localhost:8000/preview/abc123
    • Manual Override: Can specify custom base_url parameter if needed

    Environment Variables:

    bash
    # 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):

    bash
    # 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

    Session Management & Artifact Storage

    The server includes enterprise-grade session management and artifact storage powered by [chuk-artifacts](https://github.com/chrishayuk/chuk-artifacts):

    Features:

    • 🔒 Session isolation - Each session only sees their own drafts
    • 📦 Artifact storage - Secure, session-based storage with grid architecture
    • 🔗 Presigned URLs - Time-limited, secure preview URLs
    • ☁️ Multiple backends - Memory, filesystem, S3, IBM Cloud Object Storage
    • 🧹 Auto cleanup - Automatic expiration of old previews

    Session-Based Drafts

    python
    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

    Artifact-Based Previews

    Generate secure preview URLs with automatic expiration:

    python
    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}")

    MCP Tool: linkedin_preview_url

    The linkedin_preview_url tool generates session-isolated preview URLs:

    json
    {
      "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:

    json
    {
      "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.

    Storage Providers

    Configure storage backend based on your needs:

    Memory (Default):

    python
    # Fast, ephemeral storage for development
    manager = LinkedInManager(use_artifacts=True, artifact_provider="memory")

    Filesystem:

    python
    # Persistent storage on disk
    manager = LinkedInManager(use_artifacts=True, artifact_provider="filesystem")
    # Stores in: .artifacts/linkedin-drafts/

    S3:

    bash
    # 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
    python
    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:

    python
    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"
    )

    Grid Architecture

    Artifacts use a hierarchical grid structure:

    code
    grid/
    ├── {sandbox_id}/              # "linkedin-mcp"
    │   ├── {session_id}/          # "user_alice"
    │   │   ├── {artifact_id}/     # "abc123"
    │   │   │   ├── metadata.json
    │   │   │   └── content
    │   │   └── {artifact_id}/
    │   └── {session_id}/
    └── {sandbox_id}/

    This ensures:

    • ✅ Session isolation (users can't access each other's artifacts)
    • ✅ Multi-tenant support (different sandboxes)
    • ✅ Scalable storage (efficient organization)
    • ✅ Easy cleanup (delete by session or sandbox)

    Local Development

    For local development without cloud storage:

    python
    # 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/
    )

    Available Themes

    10 pre-built themes for different LinkedIn personas:

    ThemeDescriptionUse Case
    thought_leaderAuthority and expertiseIndustry insights, frameworks
    data_drivenLet numbers tell storyAnalytics, research, reports
    storytellerNarrative-drivenPersonal experiences, case studies
    community_builderFoster conversationPolls, questions, engagement
    technical_expertDeep technical knowledgeEngineering, dev, technical topics
    personal_brandAuthentic connectionBehind-the-scenes, personal stories
    corporate_professionalPolished corporateOfficial announcements, updates
    contrarian_voiceChallenge status quoControversial takes, debate
    coach_mentorGuide and supportTips, advice, mentorship
    entertainerMake LinkedIn funHumor, memes, light content

    MCP Server Integration

    With OAuth (Recommended)

    For HTTP mode with OAuth authentication:

    json
    {
      "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:

    bash
    uvx mcp-cli --server linkedin --provider openai --model gpt-5-mini

    See docs/OAUTH.md for complete OAuth setup instructions.

    STDIO Mode (Desktop Clients)

    For Claude Desktop and other desktop client integration:

    json
    {
      "mcpServers": {
        "linkedin": {
          "command": "linkedin-mcp",
          "args": ["stdio"]
        }
      }
    }

    Note: OAuth is required for publishing tools. STDIO mode supports all other tools (drafting, composition, previews).

    Docker

    Quick Start

    bash
    # 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

    Makefile Commands

    bash
    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

    Environment Variables

    Create a .env file:

    env
    # ============================================================================
    # 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:

    • SESSION_PROVIDER=memory - Required for development (no Redis needed)
    • SESSION_PROVIDER=redis - Required for production (with SESSION_REDIS_URL)
    • OAuth is required - Publishing tools (linkedin_publish) require OAuth authentication
    • Token TTLs - Defaults are security-focused (short lifetimes, daily re-auth)

    See docs/OAUTH.md for complete OAuth setup and docs/DOCKER.md for Docker deployment.

    Production Deployment

    Fly.io Deployment (Recommended)

    Deploy the LinkedIn MCP server to Fly.io with Redis session storage:

    Prerequisites

    1. Fly.io Account - Sign up at fly.io

    2. Fly CLI - Install: curl -L https://fly.io/install.sh | sh

    3. LinkedIn OAuth App - Create at LinkedIn Developers

    4. Redis Instance - Create on Fly.io (or use Upstash)

    Step 1: Create Fly.io App

    bash
    # Clone repository
    git clone https://github.com/chrishayuk/chuk-mcp-linkedin.git
    cd chuk-mcp-linkedin
    
    # 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)

    Step 2: Create Redis Instance

    bash
    # Create Redis on Fly.io
    fly redis create
    
    # Note the Redis URL from output:
    # redis://default:PASSWORD@fly-INSTANCE-NAME.upstash.io:6379

    Step 3: Create Tigris Storage Bucket

    bash
    # 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

    Step 4: Configure Environment Variables

    Required Secrets Reference:

    SecretRequiredSourcePurpose
    LINKEDIN_CLIENT_ID✅ YesLinkedIn Developers PortalOAuth client ID
    LINKEDIN_CLIENT_SECRET✅ YesLinkedIn Developers PortalOAuth client secret
    SESSION_REDIS_URL✅ YesOutput from fly redis create (Step 2)Redis connection string for sessions
    SESSION_PROVIDER✅ YesSet to redisEnable Redis session backend
    OAUTH_SERVER_URL✅ YesYour Fly.io app URLOAuth discovery base URL
    LINKEDIN_REDIRECT_URI✅ Yes{OAUTH_SERVER_URL}/oauth/callbackOAuth callback endpoint
    AWS_ACCESS_KEY_IDAutofly storage create (Step 3)Tigris S3 access key (auto-set)
    AWS_SECRET_ACCESS_KEYAutofly storage create (Step 3)Tigris S3 secret (auto-set)
    AWS_ENDPOINT_URL_S3Autofly storage create (Step 3)Tigris S3 endpoint (auto-set)
    AWS_REGIONAutofly storage create (Step 3)Tigris S3 region (auto-set)

    Set required secrets with Fly CLI:

    bash
    # 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!

    Step 5: Configure fly.toml

    Update fly.toml with production settings:

    toml
    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`

    Step 6: Deploy

    bash
    # 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

    Step 7: Configure MCP Client

    Update your MCP client configuration (e.g., ~/.mcp-cli/servers.yaml):

    yaml
    servers:
      linkedin:
        url: https://your-linkedin-mcp.fly.dev  # No trailing slash!
        oauth: true

    Test the connection:

    bash
    uvx mcp-cli --server linkedin --provider openai --model gpt-5-mini

    Redis Configuration

    Development (Memory)

    For local development, use in-memory session storage:

    bash
    # .env file
    SESSION_PROVIDER=memory

    No Redis installation required. Sessions are lost when the server restarts.

    Production (Redis)

    For production, use Redis for persistent session storage:

    Option 1: Fly.io Redis (Upstash)

    bash
    # 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.)

    bash
    # Set Redis URL
    export SESSION_REDIS_URL="redis://username:password@host:port/db"
    export SESSION_PROVIDER=redis

    Environment Variables:

    env
    # 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)

    Custom Domain Setup

    Configure a custom domain for your deployment:

    Step 1: Add Domain to Fly.io

    bash
    # Add custom domain
    fly certs create linkedin.yourdomain.com
    
    # Verify DNS settings
    fly certs show linkedin.yourdomain.com

    Step 2: Update DNS

    Add DNS records (check output from previous command):

    code
    Type: CNAME
    Name: linkedin.yourdomain.com
    Value: your-linkedin-mcp.fly.dev

    Step 3: Update OAuth URLs

    bash
    # Update secrets with custom domain
    fly secrets set \
      OAUTH_SERVER_URL=https://linkedin.yourdomain.com \
      LINKEDIN_REDIRECT_URI=https://linkedin.yourdomain.com/oauth/callback

    Step 4: Update LinkedIn App

    1. Go to LinkedIn Developers

    2. Select your app

    3. Update "Redirect URLs" to match: https://linkedin.yourdomain.com/oauth/callback

    Environment Variables Reference

    Complete list of production environment variables:

    env
    # ============================================================================
    # 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

    Logging Configuration

    Control logging levels in production:

    env
    # 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.

    Monitoring & Troubleshooting

    bash
    # 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

    Health Checks

    The server includes health check endpoints:

    bash
    # 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

    Security Best Practices

    1. Never commit secrets - Use Fly secrets, not environment variables in fly.toml

    2. Use HTTPS only - Set force_https = true in fly.toml

    3. Rotate tokens regularly - LinkedIn tokens are auto-refreshed

    4. Monitor logs - Check for failed auth attempts

    5. Use custom domain - Professional appearance, easier to update

    6. Enable auto-scaling - Handle traffic spikes automatically

    7. Keep dependencies updated - Regular security updates

    Cost Optimization

    Fly.io pricing optimization tips:

    toml
    # 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:

    • Free tier: 3 shared-cpu VMs with 256MB RAM
    • Redis: ~$2/month for basic Upstash instance
    • Scaling: ~$0.02/hour per VM after free tier

    Documentation

    • **Getting Started** - Complete beginner's guide
    • **OAuth Guide** - OAuth 2.1 setup and configuration
    • **API Reference** - Full API documentation
    • **Themes Guide** - All themes and customization
    • **Design Tokens** - Token system reference
    • **Docker Guide** - Docker deployment
    • **CI/CD Guide** - Continuous integration
    • **Development Guide** - Contributing and development
    • **Architecture** - System architecture

    Examples

    Hello World: Compose → Draft → Preview URL

    The fastest way to see the complete workflow (examples/hello_preview.py):

    python
    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:

    bash
    # 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:

    code
    🚀 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...

    More Examples

    Comprehensive examples in the examples/ directory:

    bash
    # 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.

    Development

    Setup

    bash
    # Clone repository
    git clone https://github.com/chrishayuk/chuk-mcp-linkedin.git
    cd chuk-mcp-linkedin
    
    # Install dependencies
    make install
    make dev
    
    # Install pre-commit hooks
    make hooks-install

    Run Tests

    bash
    # Run all tests
    make test
    
    # Run with coverage
    make coverage
    
    # Run specific test
    uv run pytest tests/test_composition.py -v

    Code Quality

    bash
    # Format code
    make format
    
    # Run linter
    make lint
    
    # Type checking
    make typecheck
    
    # Security check
    make security
    
    # All quality checks
    make quality

    CI/CD

    bash
    # Run full CI pipeline locally
    make ci
    
    # Quick CI check
    make ci-quick
    
    # Pre-commit checks
    make pre-commit

    2025 LinkedIn Performance Data

    Based on analysis of 1M+ posts across 9K company pages:

    Top Performing Formats

    1. Document Posts (PDF) - 45.85% engagement (HIGHEST)

    • Optimal: 5-10 pages
    • Format: 1920x1920 square
    • Min font: 18pt for mobile

    2. Poll Posts - 200%+ higher reach (MOST UNDERUSED)

    • Opportunity: Least used format
    • Engagement: 3x average reach
    • Duration: 3-7 days optimal

    3. Video Posts - 1.4x engagement (GROWING)

    • Usage up 69% from 2024
    • Vertical format preferred
    • Keep under 3 minutes

    4. Image Posts - 2x more comments than text

    • Square format (1080x1080) performs best
    • Infographics and data viz trending

    5. Carousel Posts - Declining format

    • Down 18% reach, 25% engagement vs 2024
    • Keep to 5-10 slides maximum

    Optimal Post Structure

    • First 210 characters - Critical hook window
    • Ideal length: 300-800 characters
    • Hashtags: 3-5 optimal (not 10+)
    • Line breaks: Use for scannability
    • Best times: Tue-Thu, 7-9 AM / 12-2 PM / 5-6 PM

    First Hour Engagement

    • Minimum: 10 engagements (baseline)
    • Good: 50 engagements (algorithm boost)
    • Viral: 100+ engagements (maximum reach)

    Architecture

    Built on **ChukMCPServer** - a modular MCP server framework providing:

    • Zero-config deployment: Smart environment detection (local, Docker, Fly.io)
    • Production-ready defaults: Optimized workers, connection pooling, logging
    • OAuth 2.1 built-in: Discovery endpoints, token management, session handling
    • Multiple transports: STDIO for desktop clients, HTTP/SSE for API access
    code
    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

    Contributing

    Contributions welcome! Please read CONTRIBUTING.md for guidelines.

    Development Workflow

    1. Fork the repository

    2. Create feature branch (git checkout -b feature/amazing-feature)

    3. Make changes and add tests

    4. Run quality checks (make check)

    5. Commit changes (git commit -m 'Add amazing feature')

    6. Push to branch (git push origin feature/amazing-feature)

    7. Open Pull Request

    Testing

    • 96% test coverage - 1058 tests passing
    • Multiple test types - Unit, integration, component tests
    • Artifact system tests - Session isolation, preview URLs
    • CI/CD - GitHub Actions on every push
    • Pre-commit hooks - Automatic quality checks
    bash
    # Run all tests
    make test
    
    # Run with coverage
    make coverage
    
    # Open coverage report
    make coverage-html

    License

    MIT License - see LICENSE file for details.

    Credits

    Built by Christopher Hay

    Data Sources:

    • 2025 LinkedIn performance data from analysis of 1M+ posts
    • 9K company page benchmarks
    • LinkedIn API documentation

    Inspired by:

    • shadcn/ui - Component philosophy
    • CVA - Variant system
    • Model Context Protocol - MCP standard

    Support

    • Issues: GitHub Issues
    • Discussions: GitHub Discussions
    • Email: chris@chuk.ai

    Roadmap

    • [ ] Additional post types (events, newsletters)
    • [ ] LinkedIn analytics integration
    • [ ] A/B testing framework
    • [ ] Multi-account support
    • [ ] Scheduling and automation
    • [ ] Enhanced preview with real API data
    • [ ] Webhook support for notifications

    Changelog

    See CHANGELOG.md for version history.

    ---

    Similar MCP

    Based on tags & features

    • PU

      Pursuit Mcp

      Python00
    • HE

      Hello Mcp

      Python00
    • GR

      Gradle Mcp

      Python00
    • BA

      Basecamp Mcp

      Python00

    Trending MCP

    Most active this week

    • PL

      Playwright Mcp

      TypeScript·
      22.1k
    • SE

      Serena

      Python·
      14.5k
    • MC

      Mcp Playwright

      TypeScript·
      4.9k
    • MC

      Mcp Server Cloudflare

      TypeScript·
      3.0k
    View All MCP Servers

    Similar MCP

    Based on tags & features

    • PU

      Pursuit Mcp

      Python00
    • HE

      Hello Mcp

      Python00
    • GR

      Gradle Mcp

      Python00
    • BA

      Basecamp Mcp

      Python00

    Trending MCP

    Most active this week

    • PL

      Playwright Mcp

      TypeScript·
      22.1k
    • SE

      Serena

      Python·
      14.5k
    • MC

      Mcp Playwright

      TypeScript·
      4.9k
    • MC

      Mcp Server Cloudflare

      TypeScript·
      3.0k