Skip to main content
Glama
KpihX

linkedin-mcp

by KpihX

linkedin-mcp

0 Trust · 100% Control | 0 Magic · 100% Transparency | 0 Hardcoding · 100% Flexibility

A Model Context Protocol (MCP) server for LinkedIn — official OAuth 2.0, intent-first tool surface, three-level admin (CLI · HTTP · Telegram). Designed for the KpihX homelab stack; deployment-ready with Docker + Traefik.

Repos: GitHub · GitLab


Why this exists

LinkedIn has no general-purpose developer API for personal accounts. The official OAuth surface (openid profile email w_member_social) covers everything an individual power-user needs:

  • Post, delete, like, comment on content

  • Read own profile and auth status

  • Guide agents through the flow with linkedin_guide

This MCP wraps that surface with full operational visibility: a CLI admin, an HTTP admin API, and an optional Telegram bot — the same architecture as whats-mcp, tick-mcp, mail-mcp.


Related MCP server: @isteam/linkedin-mcp

Architecture

linkedin-mcp/
├── src/
│   ├── main.js          ← entry: serve (stdio) | serve-http
│   ├── admin.js         ← entry: CLI admin (linkedin-admin)
│   ├── config.js        ← loadConfig() — deep merge defaults → config.json → env
│   ├── token.js         ← save / load / clear / summary  (~/.mcps/linkedin/token.json)
│   ├── client.js        ← LinkedInClient — official REST API calls
│   ├── server.js        ← createMcpServer() — MCP SDK wiring
│   ├── http_app.js      ← Express HTTP app: /health /admin/* /mcp
│   ├── .env.example     ← required secrets template
│   └── admin/
│       ├── cli.js       ← Commander CLI (auth / status / logout / logs / health / urls
│       │                               / client-id / client-secret / token)
│       ├── service.js   ← unified admin kernel — single source of truth for all surfaces
│       ├── oauth.js     ← LinkedIn OAuth 2.0 + OIDC flow (configurable callback port)
│       └── telegram.js  ← optional Telegram bot admin (15 commands)
├── tools/
│   ├── registry.js      ← listTools() + callTool()
│   ├── guide.js         ← linkedin_guide
│   ├── profile.js       ← linkedin_get_profile, linkedin_auth_status
│   ├── posts.js         ← linkedin_create_post, linkedin_create_image_post, linkedin_delete_post
│   └── social.js        ← linkedin_like_post, linkedin_create_comment
├── tests/               ← bun test (61 tests, 0 deps on live API)
├── deploy/
│   ├── docker-compose.yml
│   └── docker-compose.override.example.yml
├── Dockerfile
├── .gitlab-ci.yml
└── config.json          ← non-secret defaults (port 8095, OAuth port 3001, state dir, scopes)

Transport strategy:

Agent / Claude / Gemini / Codex / Copilot / Vibe
         │
         ├─── HTTP (homelab) ──→  https://linkedin.kpihx-labs.com/mcp   (primary)
         └─── stdio (local)  ──→  ~/.bun/bin/linkedin-mcp serve         (fallback)

Tools (8)

Tool

Description

linkedin_guide

Orientation — all tools, auth flow, quick start

linkedin_auth_status

Token presence, validity, days remaining, profile

linkedin_get_profile

Fetch own LinkedIn profile (name, email, sub)

linkedin_create_post

Create a text post (PUBLIC or CONNECTIONS)

linkedin_create_image_post

Create a post with an attached image

linkedin_delete_post

Delete a post by URN

linkedin_like_post

Like a post by URN

linkedin_create_comment

Add a comment to a post by URN


Admin interfaces

CLI (linkedin-admin)

# Authentication
linkedin-admin auth [--port N]     # OAuth flow — opens browser, saves token (default port 3001)
linkedin-admin token set <value>   # Set access token directly (fetches profile, no browser)
linkedin-admin token unset         # Clear stored token (same as logout)
linkedin-admin logout              # Clear stored token

# Credential management (stored in admin env file)
linkedin-admin client-id set <v>   # Set LINKEDIN_CLIENT_ID
linkedin-admin client-id unset     # Clear LINKEDIN_CLIENT_ID
linkedin-admin client-secret set <v>
linkedin-admin client-secret unset

# Status & observability
linkedin-admin status              # Credentials table + token summary
linkedin-admin status --json       # Machine-readable JSON
linkedin-admin logs [--lines 50]   # Tail admin log
linkedin-admin health              # HTTP server reachability
linkedin-admin urls                # All public/private endpoints

HTTP admin

Endpoint

Method

Description

/health

GET

Liveness probe

/admin/status

GET

Full runtime status (token + Telegram state)

/admin/help

GET

All commands reference

/admin/logs?lines=50

GET

Recent admin log tail

/admin/client-id/set

POST

Body: { "value": "..." }

/admin/client-id/unset

POST

Clear LINKEDIN_CLIENT_ID

/admin/client-secret/set

POST

Body: { "value": "..." }

/admin/client-secret/unset

POST

Clear LINKEDIN_CLIENT_SECRET

/admin/token/set

POST

Body: { "value": "..." } — sets token + fetches profile

/admin/token/unset

POST

Clear stored token

/mcp

POST/GET/DELETE

MCP Streamable HTTP transport

Telegram bot (optional)

Set TELEGRAM_LINKEDIN_HOMELAB_TOKEN + TELEGRAM_CHAT_IDS — server polls every 5 s.

Command

Action

/start /help

Full command reference

/status

Credentials + token status

/health

HTTP server health

/urls

Endpoint map

/logs [n]

Last n admin log lines (default 20)

/token_set <value>

Set access token (fetches profile)

/token_unset

Clear stored token

/client_id_set <value>

Set LINKEDIN_CLIENT_ID

/client_id_unset

Clear LINKEDIN_CLIENT_ID

/client_secret_set <value>

Set LINKEDIN_CLIENT_SECRET

/client_secret_unset

Clear LINKEDIN_CLIENT_SECRET


Quick start

1. LinkedIn App setup

  1. Go to LinkedIn Developer Portal

  2. Create an app → add products: "Sign In with LinkedIn using OpenID Connect" + "Share on LinkedIn"

  3. Set redirect URI: http://localhost:3001/callback

  4. Note Client ID and Client Secret

2. Configure secrets

# Option A — via admin CLI (written to admin env file)
linkedin-admin client-id set <your_client_id>
linkedin-admin client-secret set <your_client_secret>

# Option B — via .env file
cp src/.env.example src/.env
# Fill in LINKEDIN_CLIENT_ID and LINKEDIN_CLIENT_SECRET

# Option C — via bw-env / kshrc (injected at login shell)

3. Install & authenticate

bun install
bun link              # editable install: ~/.bun/bin/linkedin-mcp + linkedin-admin

# Full OAuth flow (browser consent)
linkedin-admin auth   # default port 3001
linkedin-admin auth --port 3002  # if 3001 is busy

# Or set access token directly (server/headless contexts)
linkedin-admin token set <your_access_token>

linkedin-admin status # verify credentials and token

4. Start MCP server

# stdio (for agent config)
linkedin-mcp serve

# HTTP (for homelab deployment)
linkedin-mcp serve-http

Docker / Homelab deployment

cd deploy
cp docker-compose.override.example.yml docker-compose.override.yml
# Edit override with your secrets (or rely on CI env injection)

docker compose up -d

Traefik labels in deploy/docker-compose.yml expose:

  • https://linkedin.kpihx-labs.com (primary private trusted route)

  • https://linkedin.homelab (fallback)

The container persists all state in /data (Docker volume):

  • /data/state/token.json — OAuth token

  • /data/linkedin-admin.env — credentials written by token set / client-id set


Agent registration

Claude Code (~/.claude.json)

"linkedin-mcp": {
  "type": "http",
  "url": "https://linkedin.kpihx-labs.com/mcp"
},
"linkedin-mcp--fallback": {
  "command": "/home/kpihx/.bun/bin/linkedin-mcp",
  "args": ["serve"]
}

Gemini (~/.gemini/extensions/linkedin_mcp/gemini-extension.json)

{
  "name": "linkedin-mcp",
  "version": "0.2.1",
  "mcpServers": {
    "linkedin-mcp": { "httpUrl": "https://linkedin.kpihx-labs.com/mcp" },
    "linkedin-mcp--fallback": {
      "command": "/home/kpihx/.bun/bin/linkedin-mcp",
      "args": ["serve"]
    }
  }
}

Codex (~/.codex/config.toml)

[mcp_servers.linkedin_mcp]
url = "https://linkedin.kpihx-labs.com/mcp"

[mcp_servers.linkedin_mcp_fallback]
command = "/home/kpihx/.bun/bin/linkedin-mcp"
args = ["serve"]

Copilot (~/.copilot/mcp-config.json)

"linkedin_mcp": { "type": "http", "url": "https://linkedin.kpihx-labs.com/mcp" },
"linkedin_mcp_fallback": {
  "type": "stdio",
  "command": "/home/kpihx/.bun/bin/linkedin-mcp",
  "args": ["serve"]
}

Vibe (~/.vibe/config.toml)

[[mcp_servers]]
name = "linkedin"
transport = "http"
url = "https://linkedin.kpihx-labs.com/mcp"

[[mcp_servers]]
name = "linkedin_fallback"
transport = "stdio"
command = "/home/kpihx/.bun/bin/linkedin-mcp"
args = ["serve"]

Tests

bun test           # 61 tests, 5 files, 0 live API calls
bun test --watch   # watch mode

All tests use mocked fetch and temp directories — no credentials needed.


Token lifecycle

LinkedIn personal OAuth tokens expire in 60 days. There is no refresh token for non-Partner apps.

Day 0   →  linkedin-admin auth          (OAuth flow, browser consent)
           OR  linkedin-admin token set  (direct token, headless/server)
Day 55+ →  linkedin-admin status        (shows days_left)
Day 60  →  token invalid, re-run auth

Both flows produce a complete token.json with member_id populated — required for all posting tools.


License

MIT — see LICENSE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Fully featured MCP server that provides automation tools for LinkedIn, supporting browser-based scraping and API-based operations for content management, media uploads, and reactions.
    6
    3
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    MCP server for LinkedIn API integration. Enables authentication, profile access, connections, search, messaging, and feed management via OAuth2.
    -