Skip to main content
Glama

mcp-linkedin

An MCP server that lets AI assistants publish to LinkedIn on your behalf.

mcp-linkedin MCP server

What it does

This is a Model Context Protocol (MCP) server that wraps the Unipile API to give AI assistants (Claude Code, Claude Desktop, or any MCP-compatible client) the ability to create posts, comments, and reactions on LinkedIn. The AI writes the content; this tool handles the publishing. All publishing actions default to preview mode — nothing goes live without explicit confirmation.

Related MCP server: LinkedIn MCP Server

Features

  • 3 tools: publish, comment, react

  • Dry run by default (preview before publishing)

  • Auto-likes posts immediately after publishing

  • Media attachments (local files or URLs — images and video)

  • Company @mentions (auto-resolved via Unipile)

  • Works with Claude Code, Claude Desktop, and any MCP client

Prerequisites

  • Node.js 18+ — uses ES modules, node:test, and top-level await

  • Unipile accountUnipile is the service that connects to LinkedIn's API. Sign up, connect your LinkedIn account, and get your API key and DSN from the dashboard.

Installation

git clone https://github.com/timkulbaev/mcp-linkedin.git
cd mcp-linkedin
npm install

Configuration

Claude Code

Add to ~/.claude/mcp.json:

{
  "mcpServers": {
    "linkedin": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-linkedin/index.js"],
      "env": {
        "UNIPILE_API_KEY": "your-unipile-api-key",
        "UNIPILE_DSN": "apiXX.unipile.com:XXXXX"
      }
    }
  }
}

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):

{
  "mcpServers": {
    "linkedin": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-linkedin/index.js"],
      "env": {
        "UNIPILE_API_KEY": "your-unipile-api-key",
        "UNIPILE_DSN": "apiXX.unipile.com:XXXXX"
      }
    }
  }
}

Restart Claude Code or Claude Desktop after editing the config.

Environment variables

Variable

Required

Description

UNIPILE_API_KEY

Yes

Your Unipile API key (from the Unipile dashboard)

UNIPILE_DSN

Yes

Your Unipile DSN (e.g. api16.unipile.com:14648)

These are passed via the MCP config, not a .env file. The server reads them from process.env at startup.

Tools

linkedin_publish

Creates an original LinkedIn post.

dry_run defaults to true. Call with dry_run: true first to get a preview, then call again with dry_run: false to actually publish.

Parameter

Type

Required

Default

Description

text

string

yes

Post body, max 3000 characters

media

string[]

no

[]

Local file paths or URLs (jpg, png, gif, webp, mp4)

mentions

string[]

no

[]

Company names to @mention (auto-resolved)

dry_run

boolean

no

true

Preview without publishing

Preview response (dry_run: true):

{
  "status": "preview",
  "post_text": "Hello LinkedIn!",
  "character_count": 16,
  "character_limit": 3000,
  "media": [],
  "mentions": [],
  "warnings": [],
  "ready_to_publish": true
}

Publish response (dry_run: false):

{
  "status": "published",
  "post_id": "7437514186450104320",
  "post_text": "Hello LinkedIn!",
  "posted_at": "2026-03-11T15:06:04.849Z",
  "auto_like": "liked"
}

After publish, save the post_id and construct the post URL:

https://www.linkedin.com/feed/update/urn:li:activity:{post_id}/

linkedin_comment

Posts a comment on an existing LinkedIn post.

dry_run defaults to true.

Parameter

Type

Required

Default

Description

post_url

string

yes

LinkedIn post URL or raw URN (urn:li:activity:... or urn:li:ugcPost:...)

text

string

yes

Comment text

dry_run

boolean

no

true

Preview without posting


linkedin_react

Reacts to a LinkedIn post. This action is immediate — there is no dry_run.

Parameter

Type

Required

Default

Description

post_url

string

yes

LinkedIn post URL or raw URN

reaction_type

string

no

"like"

One of: like, celebrate, support, love, insightful, funny


How it works

                    ┌──────────────────────────────────┐
                    │           mcp-linkedin            │
AI Assistant  ──►   │                                  │
(via MCP stdio)     │  Posts/Comments/Reactions  ──►  Unipile API  ──►  LinkedIn
                    └──────────────────────────────────┘
  • The AI assistant calls tools via MCP's JSON-RPC protocol over stdio

  • Calls Unipile API which handles LinkedIn OAuth — no token management needed

Safe publishing workflow

The dry_run default exists to prevent accidental publishing. The intended flow:

  1. AI calls the tool with dry_run: true (the default)

  2. You see the preview: final text, character count, media validation, resolved mentions, warnings

  3. You confirm or ask for changes

  4. AI calls again with dry_run: false

  5. Post goes live

dry_run is true by default. The AI cannot publish without explicitly setting it to false, which requires going through the preview step first.

Media handling

  • Pass local file paths (/path/to/image.jpg) or URLs (https://example.com/img.png)

  • URLs are downloaded to /tmp/mcp-linkedin-media/ and cleaned up after publish (whether it succeeds or fails)

  • Supported formats: jpg, jpeg, png, gif, webp (images), mp4 (video)

  • Each file is validated before upload: must exist, be non-empty, and be a supported type

  • Failed files appear in the preview's media array with "valid": false and an error message

Company @mentions

  • Pass company names as strings: mentions: ["Microsoft", "OpenAI"]

  • The server slugifies each name and looks it up via Unipile's LinkedIn company search

  • Resolved companies are injected as {{0}}, {{1}} placeholders in the post text — LinkedIn renders these as clickable @mentions

  • If a company name appears in the post text, it gets replaced in place; if not, the placeholder is appended

  • Unresolved names appear as warnings in the preview. The post can still be published without them.

Testing

npm test       # 28 unit tests, zero extra dependencies (Node.js built-in test runner)
npm run lint   # Biome linter

Project structure

mcp-linkedin/
  index.js                    Entry point (stdio transport)
  package.json
  src/
    server.js                 MCP server and tool registration
    unipile-client.js         Unipile API wrapper (posts, comments, reactions)
    media-handler.js          URL download and file validation
    tools/
      publish.js              linkedin_publish handler
      comment.js              linkedin_comment handler
      react.js                linkedin_react handler
  tests/
    unit.test.js              28 unit tests

Getting a Unipile account

  1. Sign up for a Unipile account

  2. In the dashboard, connect your LinkedIn account

  3. Copy your API key and DSN from the dashboard settings

  4. Paste them into the MCP config (see Configuration above)

Unipile has a free tier that covers basic usage.

License

MIT — see LICENSE.

Credits

Built by Timur Kulbaev. Uses the Model Context Protocol by Anthropic and the Unipile API.

Available Tools

3 tools
linkedin_commentA

Post a comment on a LinkedIn post via Unipile. IMPORTANT: dry_run defaults to true — this returns a preview of the comment without posting it. WORKFLOW: 1) Call with dry_run=true, 2) Show preview to user, 3) Get confirmation, 4) Call with dry_run=false. Accepts a LinkedIn post URL (e.g. https://linkedin.com/feed/update/urn:li:activity:12345) or a raw URN (urn:li:activity:12345 or urn:li:ugcPost:67890).

ParametersJSON Schema
NameRequiredDescriptionDefault
post_urlYesLinkedIn post URL (linkedin.com/feed/update/...) or raw URN (urn:li:activity:... or urn:li:ugcPost:...)
textYesComment text to post
dry_runNoDEFAULT TRUE. When true, returns a preview without posting. Set to false only after user confirms.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses that dry_run defaults to true and that it returns a preview without posting. Annotations are absent, so description carries full burden. Could mention error handling or authentication, but adequately covers core behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Concise, well-structured with a numbered workflow. Every sentence adds value. No fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers the two-step workflow and input formats. No output schema, but the description implies a preview is returned. Could mention expected return format or error cases, but sufficient for the tool's simplicity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with decent descriptions. The description reinforces the dry_run default and acceptable URL formats, but adds little beyond the schema. Baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states 'Post a comment on a LinkedIn post via Unipile.' Differentiates from siblings (linkedin_publish, linkedin_react) by focusing on commenting vs. publishing or reacting.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides a concrete workflow (dry_run first, then confirm, then post) and explains acceptable input formats. Missing explicit guidance on when not to use this tool (e.g., use linkedin_react for reactions), but still highly actionable.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_publishA

Publish an original post to LinkedIn via Unipile. IMPORTANT: dry_run defaults to true — this returns a preview showing the formatted text, resolved mentions, validated media, and character count. Review the preview carefully, then call again with dry_run=false to actually publish. Supports text (max 3000 chars), media attachments (local file paths or URLs to images/videos: jpg, png, gif, webp, mp4), and company @mentions (pass company names — they are resolved automatically via Unipile and injected as {{0}}, {{1}} placeholders). WORKFLOW: 1) Call with dry_run=true, 2) Present preview to user, 3) Get confirmation, 4) Call with dry_run=false.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYesPost body text. Maximum 3000 characters. Include company names here if you want them @mentioned — they will be replaced with Unipile placeholders automatically.
account_idNoOptional. Unipile account ID to post from. If omitted, the first LinkedIn account found in Unipile is used (default behavior).
mediaNoOptional. Array of local file paths or URLs to attach. Supported formats: jpg, png, gif, webp, mp4. URLs are downloaded to /tmp automatically.
mentionsNoOptional. Array of company names to @mention (e.g. ["Microsoft", "OpenAI"]). Each name is resolved to a LinkedIn company ID via Unipile.
dry_runNoDEFAULT TRUE. When true, returns a preview without publishing. Set to false only after user confirms the preview.

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must fully disclose behavior. It explains that dry_run returns a preview with formatted text, resolved mentions, validated media, and character count, and that actual publishing requires dry_run=false. It also describes media handling and mention resolution. However, it does not discuss idempotency, error handling, or authentication needs, which would make it complete.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, well-structured paragraph that front-loads the purpose, then highlights the critical dry_run note, lists supported content, and provides a numbered workflow. Every sentence contributes necessary information without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (5 parameters, 1 required, no output schema, no annotations), the description effectively covers all parameters and the two-phase workflow. It describes the preview content, but lacks details on the response after successful publish. It is sufficiently complete for an AI agent to use the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, providing baseline 3. The description adds value by explaining the dry_run workflow implications, that mentions are resolved to placeholders, that media URLs are downloaded to /tmp, and that account_id defaults to the first LinkedIn account. This goes beyond the schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Publish an original post to LinkedIn via Unipile,' using a specific verb and resource. It distinguishes from sibling tools (linkedin_comment, linkedin_react) by focusing on original posts and the dry_run workflow.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly outlines when to use the tool, emphasizing the dry_run workflow with a step-by-step process: call with dry_run=true to preview, then with dry_run=false to publish. It provides clear guidance on not publishing without user confirmation.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

linkedin_reactA

React to a LinkedIn post via Unipile. Accepts a LinkedIn post URL (e.g. https://linkedin.com/feed/update/urn:li:activity:12345) or a raw URN (urn:li:activity:12345). This action is immediate — there is no dry_run. Reaction type defaults to 'like' if not specified.

ParametersJSON Schema
NameRequiredDescriptionDefault
post_urlYesLinkedIn post URL (linkedin.com/feed/update/...) or raw URN (urn:li:activity:...)
reaction_typeNoReaction type. One of: like, celebrate, support, love, insightful, funny. Defaults to "like".like

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses key behavioral traits: it is immediate with no dry run, and the reaction type defaults to 'like'. While no annotations are provided, the description offers sufficient transparency for a simple mutation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise, consisting of two sentences that directly convey the essential information without any unnecessary words or fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with only two parameters and no output schema, the description is fairly complete. It covers the input format, default behavior, and immediate nature, though it could briefly mention that the tool does not support other actions like commenting.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Input schema coverage is 100% with clear descriptions for both parameters. The description adds minimal additional meaning, reiterating the default reaction type and the acceptable post identifier formats, but does not go beyond what the schema provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action 'React to a LinkedIn post' and specifies the resource via URL or URN. It distinguishes itself from sibling tools 'linkedin_comment' and 'linkedin_publish' by the unique action of reacting.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides context on when to use the tool (for immediate reactions) but does not explicitly contrast with sibling tools or state when not to use it. It mentions the immediate nature but lacks guidance on alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 1 tool update
    • Changedlinkedin_publish1 field changed
      • addedInput schema / properties / account_id
        Added value: +{
        +  "description": "Optional. Unipile account ID to post from. If omitted, the first LinkedIn account found in Unipile is used (default behavior).",
        +  "type": "string"
        +}
  2. 3 tool updatesv1.0.0
    • First observedlinkedin_comment
    • First observedlinkedin_publish
    • First observedlinkedin_react

TDQS

A4.1/5.0
Disambiguation5/5

Each tool targets a distinct LinkedIn action: commenting, publishing, or reacting. There is no overlap or ambiguity; an agent can clearly distinguish them.

Naming Consistency5/5

All tools follow a consistent 'linkedin_verb' pattern in snake_case. The verbs are descriptive and uniform.

Tool Count3/5

Three tools is low but acceptable for a focused engagement set. However, it feels slightly thin for a full LinkedIn integration.

Completeness3/5

The set covers posting, commenting, and reacting, but lacks delete, update, or retrieval features, leaving notable gaps for content lifecycle management.

Maintenance

ActivityInactive
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    F
    maintenance
    An MCP server for LinkedIn REST API v2 that enables AI assistants to create, list, and delete posts, manage events, upload images, comment, and react—featuring OAuth 2.0 with session persistence, local post history tracking, and multiple automated tests
    214
    7
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    A personal-use remote MCP server that lets you post to LinkedIn, queue/schedule drafts, and pull your own post analytics from Claude.
    29
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    MCP server that lets AI agents operate a real LinkedIn account in plain English with safety controls and approval queues.
    29
    1
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/timkulbaev/mcp-linkedin'

If you have feedback or need assistance with the MCP directory API, please join our Discord server