Linkedin Safe
FreeNot checkedMCP server for safe LinkedIn automation: official-API posting, comments, and likes plus guest-endpoint job search and a local application tracker.
About
MCP server for safe LinkedIn automation: official-API posting, comments, and likes plus guest-endpoint job search and a local application tracker.
README
An MCP server that gives AI agents (Claude Code, Codex, Claude Desktop, Cursor, …) LinkedIn superpowers — without putting your LinkedIn account at risk:
- Post to LinkedIn — text, links, and images via LinkedIn's official API (OAuth, ToS-compliant), plus comments and likes.
- Search jobs — keyword/location/remote/experience/date filters via LinkedIn's public guest endpoints. No login, no cookies: your account is never involved.
- Run a job hunt — a local SQLite application tracker (interested → applied → interviewing → offer) with notes and per-job posting snapshots, so an agent can manage your pipeline and write tailored cover letters even after a posting is taken down.
Why this design?
LinkedIn offers no official job-search API, and the unofficial routes (Voyager
internal API with your li_at session cookie, headless browsers on your logged-in
session) violate LinkedIn's User Agreement §8.2 and routinely get accounts
restricted. This server deliberately splits the difference:
| Concern | How it's handled | Account risk |
|---|---|---|
| Posting, comments, likes | Official REST API, your own OAuth app, w_member_social |
None — sanctioned |
| Job search & details | Guest endpoints (the logged-out jobs pages), IP-rate-limited | None — no credentials involved |
| Pipeline tracking | Local SQLite on your machine | None — never touches LinkedIn |
| Easy Apply, DMs, feed reading | Intentionally not included — impossible without ToS-violating access | — |
Requirements
- Python 3.11+ and uv
- For posting only: a free self-serve LinkedIn developer app (5-minute setup below). Job search and the tracker work with zero setup.
Install & connect to your agent
Clone/copy this directory, then register it with your MCP client. <REPO> below is
the absolute path to this project.
Claude Code
claude mcp add linkedin \
--env LINKEDIN_CLIENT_ID=your_client_id \
--env LINKEDIN_CLIENT_SECRET=your_client_secret \
-- uv run --directory <REPO> linkedin-safe-mcp
Or in a project's .mcp.json:
{
"mcpServers": {
"linkedin": {
"command": "uv",
"args": ["run", "--directory", "<REPO>", "linkedin-safe-mcp"],
"env": {
"LINKEDIN_CLIENT_ID": "your_client_id",
"LINKEDIN_CLIENT_SECRET": "your_client_secret"
}
}
}
}
Codex (~/.codex/config.toml)
[mcp_servers.linkedin]
command = "uv"
args = ["run", "--directory", "<REPO>", "linkedin-safe-mcp"]
env = { LINKEDIN_CLIENT_ID = "your_client_id", LINKEDIN_CLIENT_SECRET = "your_client_secret" }
Claude Desktop (claude_desktop_config.json) — same JSON shape as .mcp.json
above.
The LINKEDIN_CLIENT_* variables are only needed for posting; omit them if you
only want job search + tracking.
Enabling posting (one-time LinkedIn app setup)
- Go to https://www.linkedin.com/developers/apps → Create app (requires associating any LinkedIn Page; you can create a trivial one).
- On the app's Products tab, add Share on LinkedIn and Sign In with LinkedIn using OpenID Connect.
- On the Auth tab, add the redirect URL
http://127.0.0.1:8765/callback(it must be the IP literal, notlocalhost— see Security below). - Copy the Client ID and Client Secret into the env vars shown above.
- Authenticate once — either way works:
- In a terminal:
uv run --directory <REPO> linkedin-safe-mcp auth - Or just ask your agent to post something; it will call the
logintool and hand you the authorization URL.
- In a terminal:
Tokens are stored in ~/.linkedin-mcp/tokens.json, created mode 0600 inside a 0700
directory, and last ~60 days; LinkedIn doesn't issue refresh tokens to self-serve
apps, so you re-run the login when it expires (auth_status tells the agent exactly
when that is).
LinkedIn doesn't issue refresh tokens to self-serve apps, so you re-run the login
when it expires (auth_status tells the agent exactly when that is).
Tools
| Tool | Needs auth | What it does |
|---|---|---|
auth_status |
– | Reports config/auth state with exact next steps |
login / logout |
– | Browser OAuth flow / delete stored tokens |
get_my_profile |
✓ | Name, email, person URN of the connected account |
create_post |
✓ | Publish a post: text (+hashtags), optional link or local image (real PNG/JPEG/GIF, ≤10 MB); PUBLIC or CONNECTIONS |
delete_post |
✓ | Delete one of your posts (URN or post URL) |
comment_on_post |
✓ | Comment on a post (URN or post URL) |
like_post |
✓ | Like a post (URN or post URL) |
search_jobs |
– | Filters: location, remote/hybrid/onsite, time posted, experience levels, job types, Easy-Apply-only, sort; up to 50 results |
get_job |
– | Full posting: description, seniority, type, salary if listed, applicant count, external apply URL |
save_job |
– | Snapshot a job into the local tracker |
get_saved_job / list_saved_jobs |
– | One job with history / pipeline overview with status counts |
update_job_status |
– | interested → applied → interviewing → offer / rejected / withdrawn / archived, with notes |
add_job_note / remove_saved_job |
– | Append a note / drop a job |
Things agents can do with this: "find remote staff-engineer roles posted this week, save the promising ones, draft tailored cover letters from the saved descriptions, mark the ones I applied to, and post a summary of my open-source work."
Configuration
| Env var | Default | Purpose |
|---|---|---|
LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRET |
– | LinkedIn app credentials (posting only) |
LINKEDIN_MCP_DIR |
~/.linkedin-mcp |
Where tokens + tracker DB live |
LINKEDIN_REDIRECT_PORT |
8765 |
OAuth callback port (must match the app's redirect URL) |
LINKEDIN_API_VERSION |
202606 |
LinkedIn-Version header for /rest/* calls |
LINKEDIN_POSTS_BACKEND |
auto |
rest, ugc, or auto (try + remember what your app is allowed to use) |
LINKEDIN_MCP_USER_AGENT |
a Chrome UA | UA for guest job requests |
LINKEDIN_MCP_IMAGE_DIR |
unset | If set, create_post may only attach images from this directory |
Behavior notes & limits
- Posting: LinkedIn caps member posting at 150 requests/day and rejects exact duplicates of recent posts (422). Reserved characters in post text are escaped automatically for the versioned API so parentheses don't cause errors; hashtags are preserved.
- Job search: guest endpoints are rate-limited per IP (HTTP 429). The
server caches results (10 min searches / 6 h job details), retries with backoff,
and paces multi-page fetches; on a persistent 429 it returns a clear "wait a
minute" error to the agent. Keep
limitmodest. - Scraping posture: guest job search reads the same public pages a logged-out visitor sees, at human-ish rates, with caching to minimize load. Still, LinkedIn could change or gate these endpoints at any time — the parsers are pinned by fixture tests so breakage is detected loudly, and the tool errors stay agent-actionable.
Security
The threat model assumes the agent driving this server is not trusted: it reads job descriptions scraped from LinkedIn, so a hostile posting is a prompt-injection channel straight into every tool argument. The boundaries that follow from that:
- Image attachments are not a file-read primitive.
create_post(image_path=…)accepts only real PNG/JPEG/GIF files — verified by magic bytes and the format's mandatory trailer, so neither renaming a secret nor appending one after a valid header gets through — at most 10 MB, never via a symlink, hardlink, pipe or device. Without this, "attach~/.ssh/id_rsa" was a valid call that published the key. Residual limit: an attacker who can already both read a secret and write files could encode it inside a structurally valid image; no format check can prevent that. SetLINKEDIN_MCP_IMAGE_DIRto confine uploads to one folder if that matters to you. - The OAuth callback validates
statebefore anything else. The listener on127.0.0.1is reachable by any local process and by any web page the user has open, so an unauthenticated request must not be able to abort a pending login (which would also free the port for an authorization-code interceptor). Error text is HTML-escaped at the sink. - The redirect URL is
127.0.0.1, neverlocalhost. Browsers may resolve the name to::1, which a different local account can bind. RFC 8252 §8.3. - Secrets are 0600 from creation. Tokens,
state.jsonand the tracker DB are created private rather than chmod-ed afterwards, closing the window where a local watcher could read a fresh access token;~/.linkedin-mcpis 0700. - Upload targets are pinned. The Bearer token is only ever PUT to an HTTPS
linkedin.com/licdn.comhost, whatever URL the API response asks for.
These are covered by regression tests (tests/test_client_security.py, the
security sections of tests/test_oauth.py and tests/test_tracker.py) — each one
is a working exfiltration or hijack attempt that must fail closed.
Found something? Open an issue, or email the address on the GitHub profile for anything sensitive.
Development
uv sync # install deps (Python ≥3.11)
uv run pytest # 102 tests: parsers vs live fixtures, payloads, OAuth, tracker,
# security regressions, plus an end-to-end stdio smoke test
# that spawns the real server
uv run ruff check src tests && uv run ruff format --check src tests
Layout: src/linkedin_mcp/ — server.py (tool surface) · api/ (official REST:
posts, social actions, uploads, dual rest/ugc backend) · auth/ (OAuth + token
store) · jobs/ (guest client, HTML parsers, filter mappings) · tracker/
(SQLite store) · cli.py (serve | auth | status | logout).
Roadmap
- Publish to PyPI (
uvx linkedin-safe-mcpone-liner) - Reaction types beyond like; multi-image posts; poll posts
- Optional third-party job-data providers behind the same tool schema
streamable-httptransport for remote/hosted use- (Considered, opt-in only, off by default) a cookie-based Voyager provider for personalized features — with loud warnings, since it violates LinkedIn's ToS
License
MIT
Install Linkedin Safe in Claude Desktop, Claude Code & Cursor
unyly install linkedin-safe-mcpInstalls into Claude Desktop, Claude Code, Cursor & VS Code — handles npx, uvx and build-from-source repos for you.
First time? Get the CLI: curl -fsSL https://unyly.org/install | sh
Or configure manually
Run in your terminal:
claude mcp add linkedin-safe-mcp -- uvx --from git+https://github.com/AmmYoo7/linkedin-safe-mcp linkedin-safe-mcpStep-by-step: how to install Linkedin Safe
FAQ
Is Linkedin Safe MCP free?
Yes, Linkedin Safe MCP is free — one-click install via Unyly at no cost.
Does Linkedin Safe need an API key?
No, Linkedin Safe runs without API keys or environment variables.
Is Linkedin Safe hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Linkedin Safe in Claude Desktop, Claude Code or Cursor?
Open Linkedin Safe on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectAmap Maps Mcp Server
MCP server for using the AMap Maps API
by duxiaohuiSupabase
Database, auth and storage
by SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Sequential Thinking
Dynamic and reflective problem-solving through thought sequences.
Time
Time and timezone conversion capabilities.
Compare Linkedin Safe with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
