mcp-linkedin
An MCP server that lets AI assistants publish to LinkedIn on your behalf.
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.
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 awaitUnipile account — Unipile 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 installConfiguration
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 |
| Yes | Your Unipile API key (from the Unipile dashboard) |
| Yes | Your Unipile DSN (e.g. |
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 |
| string | yes | — | Post body, max 3000 characters |
| string[] | no |
| Local file paths or URLs (jpg, png, gif, webp, mp4) |
| string[] | no |
| Company names to @mention (auto-resolved) |
| boolean | no |
| 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 |
| string | yes | — | LinkedIn post URL or raw URN (urn:li:activity:... or urn:li:ugcPost:...) |
| string | yes | — | Comment text |
| boolean | no |
| Preview without posting |
linkedin_react
Reacts to a LinkedIn post. This action is immediate — there is no dry_run.
Parameter | Type | Required | Default | Description |
| string | yes | — | LinkedIn post URL or raw URN |
| string | no |
| One of: |
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:
AI calls the tool with
dry_run: true(the default)You see the preview: final text, character count, media validation, resolved mentions, warnings
You confirm or ask for changes
AI calls again with
dry_run: falsePost 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
mediaarray with"valid": falseand 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 @mentionsIf 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 linterProject 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 testsGetting a Unipile account
Sign up for a Unipile account
In the dashboard, connect your LinkedIn account
Copy your API key and DSN from the dashboard settings
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.