linkedin-mcp
linkedin-mcp
An MCP (Model Context Protocol) server for LinkedIn and job hunting: authenticate via OAuth 2.0, read your profile, draft and analyze posts with an LLM, publish posts to your feed, search free job boards, and track applications — all through tools any MCP client (Claude, Cursor, custom agents) can call.
User / agent
│ (MCP: stdio or streamable HTTP)
▼
linkedin-mcp (FastMCP + FastAPI)
│ ├── LinkedIn OAuth 2.0 (authorization code + refresh)
│ ├── LinkedIn REST API (profile, email, create post)
│ ├── Job boards (Remotive · Arbeitnow · RemoteOK · Jobicy — keyless)
│ ├── Application tracker (local JSON store)
│ └── LLM provider layer (Grok · OpenAI · Ollama · unorouter — swappable via env)
▼
LinkedIn API / job board APIs / your LLM providerWhat's implemented (Phase 1 + a taste of Phase 3)
Tool | What it does |
| Check OAuth state; returns the login URL or a clear "what's missing" message |
| Member profile (name, picture, locale, email) via OpenID Connect |
| Member email (requires the |
| Publish a text or image post via the current Posts API ( |
| Generate a LinkedIn post with the configured LLM provider |
| Expert critique + suggested rewrites of a draft via the LLM |
| Search Remotive/Arbeitnow/RemoteOK/Jobicy — free, keyless, no LinkedIn permissions |
| LLM-scored fit for listings from |
| One call → fit assessment + cover letter + recruiter DM + interview prep |
| Save an application locally (company, role, URL, status, notes) |
| List tracked applications, filterable by status |
| Move an application along: saved → applied → interview → offer/rejected |
| Prioritized view of what needs action next |
| Compare a job description against your profile: fit score, gaps, talking points |
| Tailored cover letter or recruiter DM from a job description + your profile |
| Open-to-work / job-search announcement post |
| Queue a post to be published automatically at a future time |
| List queued posts, filterable by status |
| Remove a queued post before it publishes |
| Publish a queued post immediately (handy for testing) |
Resource | Profile exposed as an MCP resource |
Job search & application tracking
LinkedIn offers no public jobs API, so search goes through four free keyless boards (verified live): Remotive, Arbeitnow, RemoteOK, and Jobicy. The typical agent flow:
search_jobs("python fastapi") → match_jobs_to_profile(listings)
→ prepare_application(jd) → track_application(...)
→ draft_job_post(...) → create_post / schedule_postApplications are stored in .data/applications.json (gitignored). Board outages
never break a search: each board's error is reported per-board under board_errors.
Image posts
Pass a local JPG/PNG/GIF path to create_post (or schedule_post): the file is
uploaded through LinkedIn's Images API (/rest/images?action=initializeUpload →
signed PUT → referenced by urn:li:image: URN in the post) and published with
optional alt_text.
Post scheduling
schedule_post stores the post in .data/scheduled_posts.json; while the web
server is running it polls every SCHEDULER_POLL_SECONDS (default 30 s) and
publishes posts whose time has arrived. publish_at is ISO-8601 — naive values
are treated as UTC, or include an offset (e.g. 2026-09-05T15:30:00+05:30).
Failures (e.g. token expired) are recorded on the entry as status="failed" with
the error, so list_scheduled_posts shows exactly what happened.
Everything the LLM does goes through a provider-agnostic layer — you switch vendors by changing one env var, no code changes:
LLM_PROVIDER=grok # or: openai | ollama | unorouterPrerequisites
Python 3.11+ (tested on 3.12)
A LinkedIn Developer account
An LLM provider key (or a local Ollama install — free)
Step 1 — Create the LinkedIn Developer App
Go to https://www.linkedin.com/developers → Create app.
Fill in the basics (name, LinkedIn Page, logo) and create it.
In Products, request:
Sign In with LinkedIn using OpenID Connect — grants
openid,profile,emailShare on LinkedIn — grants
w_member_social, required to post
In Auth, note your Client ID and Client Secret, and add the Authorized redirect URL:
(For production you'll use your deployed HTTPS URL instead — Phase 2.)
Step 2 — Configure
python -m venv .venv
source .venv/Scripts/activate # Git Bash on Windows; on macOS/Linux: .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env # then fill in the values.env essentials:
LINKEDIN_CLIENT_ID=your-client-id
LINKEDIN_CLIENT_SECRET=your-client-secret
LLM_PROVIDER=grok
GROK_API_KEY=your-xai-keyAbout xAI billing: xAI currently does not accept Indian payment cards for its API. If that's a blocker, just set
LLM_PROVIDER=openaiorLLM_PROVIDER=ollama— nothing else changes.
Step 3 — Run
Web mode (personal dashboard UI + OAuth + MCP over HTTP):
python -m linkedin_mcp web
# or: uvicorn linkedin_mcp.app:app --reloadOpen http://localhost:8000/ for the personal dashboard — no MCP client needed:
Compose & publish posts with an image picker, or schedule them for later
Upload documents (PDF/DOCX/TXT/MD, 10 MB max) — mark your resume as profile and every job-matching tool uses your real skills instead of bare profile metadata
Job search across the four boards + one-click LLM match against your profile
Application tracker with status buttons (saved → applied → interview → …)
Live scheduled-posts list with publish-now / cancel
The MCP endpoint is still available for clients at:
http://localhost:8000/mcp/Stdio mode (for Claude Desktop / local MCP clients):
python -m linkedin_mcpStep 4 — Connect an MCP client
Claude Desktop / Claude Code claude mcp add example (streamable HTTP):
claude mcp add linkedin --transport http http://localhost:8000/mcp/Or, from any JSON-RPC client, call auth_status first — it tells you exactly what's
missing (credentials, login, tokens) instead of failing blindly.
OAuth & token handling
Authorization-code flow with a per-login
state(validated on callback; 10 min TTL).Access + refresh tokens are persisted to
LINKEDIN_TOKEN_FILE(default.data/tokens.json).Expiry/refresh handling: every API call goes through
TokenManager, which transparently refreshes the access token when it's within 5 minutes of expiry. If no refresh token is available (LinkedIn grants those to approved apps only), tools raise a clear "re-authorize" error.The current LinkedIn API version header (
Linkedin-Version) is configurable viaLINKEDIN_API_VERSION— bump it when LinkedIn sunsets the version you use (check https://learn.microsoft.com/en-us/linkedin/marketing/).
LLM provider swap
| Requires | Notes |
|
| xAI, OpenAI-compatible ( |
|
|
|
| local Ollama running |
|
|
| any other OpenAI-compatible endpoint ( |
All three share one implementation (OpenAICompatProvider) because they all speak
OpenAI's chat-completions protocol; the factory in llm/factory.py validates the key
and produces a provider with a helpful error if you haven't configured one.
Project layout
src/linkedin_mcp/
├── config.py # pydantic-settings, all env vars, provider selection
├── errors.py # ConfigurationError, NeedsReauthError, LinkedInAPIError, LLMError
├── services.py # shared wiring (store, oauth, token manager, client, scheduler)
├── scheduling.py # scheduled-post store + background publisher loop
├── jobs.py # keyless job-board search (Remotive/Arbeitnow/RemoteOK/Jobicy)
├── applications.py # local application tracker store
├── documents.py # uploads (pdf/docx/txt/md) + resume/profile summary
├── api.py # REST endpoints backing the dashboard UI
├── server.py # FastMCP server: 23 tools + resources
├── app.py # FastAPI app: /auth/* routes + mounted /mcp endpoint
├── __main__.py # CLI: `python -m linkedin_mcp [web]`
├── llm/ # swappable LLM layer (base protocol, factory, provider)
└── linkedin/ # OAuth flow, token storage/refresh, REST client
tests/ # pytest (mocked HTTP), 66 testsSecurity notes
Never commit
.envor.data/— both are gitignored.Tokens are stored in plaintext on disk (local dev). For a deployed version, move them to an encrypted store / server-side secret manager.
The OAuth
stateregistry is in-memory — fine for a single-user local app; replace with a signed cookie or session store when deploying.The LLM layer only ever sends what you pass to
draft_post/analyze_postto the provider — the server never silently uploads your LinkedIn data.
Tests
pytest # 59 tests: tokens/refresh, LLM factory, LinkedIn client,
# job boards, application tracker, tools (all mocked HTTP)
ruff check src tests # lintRoadmap
Phase 2 — free hosting: containerize, deploy on a free tier (limits change often — verify current offers), HTTPS, point
LINKEDIN_REDIRECT_URIat the deployed URL.Phase 3 — more tools:
get_post_statistics(requires approved analytics permissions), video/document posts, an agent loop where the LLM picks tools itself, Adzuna board (free key, aggregates thousands of sites incl. India locations), resume storage + per-JD tailoring.Hardening: signed-cookie state, token encryption, rate-limit handling with retries.
License
MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/iThinkAndCode/mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server