linkedin-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@linkedin-mcpdraft a LinkedIn post about our new Python 3.13 release and prepare it for approval"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
linkedin-mcp
Approval-gated LinkedIn tools for any MCP-compatible AI agent.
linkedin-mcp is a Model Context Protocol server that lets AI agents (Claude Desktop, Claude Code, Cursor, or your own agent framework) publish to LinkedIn on your behalf, without ever posting something you didn't approve.
It uses LinkedIn's official OAuth API, has zero third-party dependencies, and puts a human-in-the-loop safety layer in front of every write.
Why
Giving an autonomous agent write access to your professional profile is risky. Models hallucinate, retry on errors, and can be steered by prompt injection. This server is built around one rule:
The agent can propose. Only you can publish.
Every LinkedIn write is split into two phases:
Prepare. The agent calls
linkedin_prepare_action. The server builds the exact HTTP request LinkedIn will receive, stores it, and returns a human-readable preview plus a SHA-256 digest. Nothing is sent.Execute. After you approve that exact preview, the agent calls
linkedin_execute_actionwith the proposal ID and digest. The server re-verifies everything and sends the request once.
sequenceDiagram
participant U as You
participant A as AI agent
participant S as linkedin-mcp
participant L as LinkedIn API
A->>S: linkedin_prepare_action(post, "…")
S-->>A: preview + proposal_id + digest (nothing sent)
A->>U: "Here's the exact post. Publish it?"
A->>S: linkedin_execute_action(proposal_id, digest)
S->>U: Approval prompt in your MCP client (elicitation)
U-->>S: Approve
S->>L: POST /rest/posts (exactly the approved request, once)
L-->>S: 201 Created
S-->>A: receipt + post URLRelated MCP server: LinkedIn MCP Server
Features
Publishing: text posts and link-card posts, with
PUBLICorCONNECTIONSvisibility.Engagement: comment on a post, or react to it (Like, Celebrate, Support, Insightful, Love, Funny).
Cleanup: delete a post you published.
Human-in-the-loop by design:
Approval asked by the server: when your client supports MCP elicitation, the server itself asks you to confirm, so the model cannot approve its own action.
Exact-match approval: the digest binds the request body, endpoint and LinkedIn account. If anything changes after preparation, execution is refused.
Single-use, expiring proposals: 30 minutes by default. A proposal is consumed before the network call.
Never double-posts: network errors, timeouts and 5xx responses are recorded as
unknownand never retried. The same action can't be re-prepared for 24 hours.Daily write cap: 40 a day by default, below LinkedIn's member limits.
Private local state: the token and proposals live in
~/.linkedin-mcp, created0700. Files are0600and written atomically, and the server refuses files other users could have tampered with.Zero dependencies: pure Python standard library, Python 3.9+.
Quick start
1. Create a LinkedIn developer app (5 minutes, free)
Go to https://www.linkedin.com/developers/apps and click Create app. You'll need a LinkedIn Page to associate it with; any page you manage works.
Under Products, add Share on LinkedIn and Sign In with LinkedIn using OpenID Connect.
Under Auth → Authorized redirect URLs, add:
http://localhost:8765/callbackCopy the Client ID and Client Secret.
2. Install and connect your account
With uv (recommended):
export LINKEDIN_CLIENT_ID="your-client-id"
export LINKEDIN_CLIENT_SECRET="your-client-secret"
uvx --from git+https://github.com/PeterEkwere/linkedin-mcp linkedin-mcp loginOr with pip:
pip install git+https://github.com/PeterEkwere/linkedin-mcp
linkedin-mcp loginYour browser opens LinkedIn's consent screen. After you allow access, the token is saved to ~/.linkedin-mcp/token.json. The client ID and secret are only needed for login, not for running the server.
On a headless server, use linkedin-mcp login --paste and paste the redirect URL back into the terminal.
3. Add it to your agent
Claude Code
claude mcp add linkedin -- uvx --from git+https://github.com/PeterEkwere/linkedin-mcp linkedin-mcpClaude Desktop (claude_desktop_config.json) and Cursor (~/.cursor/mcp.json)
{
"mcpServers": {
"linkedin": {
"command": "uvx",
"args": ["--from", "git+https://github.com/PeterEkwere/linkedin-mcp", "linkedin-mcp"]
}
}
}Any other MCP client or custom agent: launch linkedin-mcp (or python -m linkedin_mcp) as a stdio server.
⚠️ Never put
linkedin_execute_actionon an auto-approve or always-allow list. In clients without elicitation support, the client's own "allow this tool call?" prompt is your approval step.
4. Try it
"Draft a LinkedIn post announcing that I just open-sourced linkedin-mcp, show it to me, and publish it if I approve."
Tools
Tool | Writes to LinkedIn? | Purpose |
| No | Connected member, token days left, writes today, approval mode, capabilities |
| No | Build and store an exact action; returns |
| Yes, once | Perform a prepared action after approval; returns a receipt (and post URL) |
| No |
|
| No | Permanently cancel a pending proposal |
linkedin_prepare_action arguments:
| Required | Optional |
|
|
|
|
| none |
|
|
|
|
| none |
post_url accepts a LinkedIn post URL or a URN such as urn:li:share:7123….
Tools carry MCP annotations (readOnlyHint, destructiveHint, openWorldHint), so clients can show risk levels.
Approval modes
Set with LINKEDIN_MCP_APPROVAL:
Mode | Behaviour |
| Uses elicitation if the client supports it; otherwise relies on the client's tool-call confirmation |
| Strict: the server must ask you itself. Execution is refused on clients without elicitation support |
| Never elicits; relies entirely on the client's per-tool confirmation |
Configuration
Variable | Default | Description |
| none | Your LinkedIn app credentials (used by |
|
| Private state directory (token, proposals, usage) |
|
|
|
|
| Maximum LinkedIn writes per UTC day |
|
| Seconds before an unapproved proposal expires |
|
| Local OAuth callback port (must match your app's redirect URL) |
|
| LinkedIn REST API version header ( |
CLI
linkedin-mcp # run the MCP server on stdio (what your client launches)
linkedin-mcp login # connect your LinkedIn account (OAuth)
linkedin-mcp status # show connection, token expiry and today's usage
linkedin-mcp logout # delete the stored tokenSecurity model
Threat | Mitigation |
Model publishes without consent | Two-phase writes; server-side elicitation approval; execute tool marked destructive |
Prompt injection alters content after approval | Digest over the exact request + account; recomputed at execution |
Agent retries and double-posts | Proposal consumed before sending; uncertain results never retried; 24 h duplicate block |
Runaway agent loops | Daily write cap; proposals expire |
Token theft on shared machines | Token in a |
Redirect or open-redirect tricks | API client refuses HTTP redirects; OAuth |
Limitations
What LinkedIn's API allows: reading your feed, inbox, connections or search, and sending messages or invitations, aren't available to self-serve apps, so this server doesn't offer them.
linkedin_statussays so.Token renewal: member tokens last about 60 days, with no refresh token for self-serve apps. Run
linkedin-mcp loginagain whenlinkedin_statusshows few days left.Single LinkedIn account: one account per state directory. Use separate
LINKEDIN_MCP_HOMEvalues for multiple accounts.
Development
git clone https://github.com/PeterEkwere/linkedin-mcp && cd linkedin-mcp
pip install -e .
python -m unittest discover -s tests -vThe tests cover request building and validation, the proposal lifecycle (exact-match, single-use, expiry, tamper detection, no-retry on uncertain outcomes, daily cap), private storage, OAuth state checks, and the MCP handshake including elicitation approve and decline flows.
Roadmap
Streamable HTTP transport for remote and hosted agents
Image and document posts
Optional research and career-tracking module (opt-in, clearly separated from the official API)
Background
linkedin-mcp began as the LinkedIn layer of Grace, my personal multi-agent assistant, where approvals happen over WhatsApp. This repository is the standalone, client-agnostic version of its official-API tools.
License
MIT © Peter Udeme Ekwere
Available Tools
5 toolslinkedin_cancel_actionA
Cancel a pending proposal so it can never be executed. Does not undo anything already published.
| Name | Required | Description | Default |
|---|---|---|---|
| proposal_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=false, destructiveHint=false and openWorldHint=false, so the safety profile is covered. The description adds real value beyond that by clarifying scope: the cancellation is irreversible for pending items but does NOT touch anything already published, which prevents an agent from assuming it retracts live content.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, zero filler, with the core action front-loaded and the scope caveat immediately following. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter cancel tool whose annotations cover the safety profile and which has no output schema, the description supplies the essential behavioral context. It omits error/edge behavior (e.g. cancelling an already-executed proposal) but nothing critical to correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% for the single parameter, so the description carries the burden and it says nothing about proposal_id. The name is largely self-evident and the 32-char length constraint hints at an opaque ID, so it remains minimally viable but uncompensated.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (cancel) and resource (pending proposal) and adds the operative effect ('so it can never be executed'). This clearly separates it from linkedin_execute_action and linkedin_prepare_action without needing to open any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'pending proposal' implies the applicable state and the description scopes the outcome, but it never states when to prefer this over alternatives (e.g. how it relates to linkedin_status or linkedin_list_actions) or what preconditions must hold. Usage is inferable rather than explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_execute_actionADestructive
Perform one prepared LinkedIn action exactly once, after the user approved its exact preview. Requires proposal_id and digest from linkedin_prepare_action. The server asks the user to confirm when the client supports it. Never call it again for a result reported as unknown.
| Name | Required | Description | Default |
|---|---|---|---|
| digest | Yes | ||
| proposal_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true, idempotentHint=false, and openWorldHint=true, so the safety profile is covered. The description adds genuinely non-redundant behavior: exactly-once execution semantics, the server-driven confirmation prompt when the client supports it, and a specific no-retry rule for unknown results. It does not describe what a successful execution returns or the observable side effects, keeping it below 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the core action and cardinality, then preconditions, then the retry prohibition. No filler or restated schema detail.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive, non-idempotent, open-world write with no output schema, the description covers the prerequisite, approval gate, confirmation flow, and retry safety. What is missing is a sense of the observable effect of the action and any failure modes beyond 'unknown', which would round it out.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must carry the burden. It tells the agent both parameters originate from linkedin_prepare_action, which is real added meaning, but it never explains that proposal_id is a 32-char and digest a 64-char token or what the digest binds to. Partial compensation for a total coverage gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Perform) and resource (one prepared LinkedIn action) plus the cardinality constraint (exactly once) and the precondition (user approved its exact preview). This clearly distinguishes it from linkedin_prepare_action, linkedin_list_actions, and linkedin_cancel_action.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit precondition chain: values come from linkedin_prepare_action and the action must be called only after approval of the exact preview. It also gives a when-not rule ('Never call it again for a result reported as unknown'). It stops short of naming sibling alternatives for other flows, so 4 rather than 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_list_actionsARead-only
List pending proposals awaiting approval, or recent actions with their final status and receipts. Never writes to LinkedIn.
| Name | Required | Description | Default |
|---|---|---|---|
| which | No | pending |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so the agent knows this is a safe local read. The description adds the clarification 'Never writes to LinkedIn,' reinforcing the read-only nature, but doesn't describe rate limits, pagination, or what receipts entail.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, front-loaded with the purpose and its two modes, and a clear negative constraint. No waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only listing tool with annotations covering safety, the description is adequate but incomplete: it doesn't clarify output shape (no output schema), pagination, or how 'receipts' are represented. The 'which' enum values are described but not exhaustively mapped to behaviors.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, but there is only one enum parameter with default 'pending'. The description lists the two possible values' meanings ('pending proposals awaiting approval' and 'recent actions with final status and receipts'), which adds some semantic clarity. However, it doesn't explain the distinction between 'pending' and omitted default explicitly. Baseline for 1 param with good description is 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb (List) and resource (pending proposals/recent actions), and clearly distinguishes the two modes. It doesn't explicitly name a sibling tool, but the 'never writes' clause helps distinguish it from linkedin_execute_action and linkedin_prepare_action.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies when to use each mode ('pending' vs 'recent') but doesn't explicitly say 'use this to check status before executing' or route to alternatives. The 'which' parameter semantics are implicit; no explicit exclusions or sibling comparisons are provided.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_prepare_actionA
Prepare (not perform) one LinkedIn action and return its exact preview, proposal_id and digest. kind=post (text, optional visibility PUBLIC|CONNECTIONS, optional link_url/link_title/link_description), comment (post_url, text), react (post_url, optional reaction), or delete_post (post_url of your own post). Always show the user the exact preview before calling linkedin_execute_action.
| Name | Required | Description | Default |
|---|---|---|---|
| kind | Yes | ||
| text | No | ||
| link_url | No | ||
| post_url | No | ||
| reaction | No | LIKE | |
| link_title | No | ||
| visibility | No | PUBLIC | |
| link_description | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations state readOnlyHint=false, destructiveHint=false, and openWorldHint=false, so the agent knows it's a non-read-only, non-destructive, closed-world operation. The description adds crucial behavioral context: it's a preparation step that returns a preview, proposal_id, and digest, and does not perform the action. However, it doesn't detail what happens if the preview is not shown or the consequences of skipping execution.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, dense sentence followed by a critical instruction. It is front-loaded with the core purpose and then lists parameter specifics. However, the parameter enumeration is somewhat lengthy and could be slightly more structured for readability, but it remains efficient with no wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given 8 parameters, no output schema, and 0% schema description coverage, the description provides essential completeness: it defines the kinds and associated parameters, and clarifies the non-execution behavior and required workflow. The only gap is that it doesn't specify the exact format of the returned preview or the digest, but that is arguably beyond the scope of a preparation tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, meaning the description must compensate. It extensively documents the parameters for each kind: for 'post', it lists text, optional visibility (PUBLIC|CONNECTIONS), optional link_url/link_title/link_description; for 'comment', post_url and text; for 'react', post_url and optional reaction; for 'delete_post', post_url. This adds significant meaning beyond the enum and string types in the schema, though it doesn't specify max lengths or default values already present in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description explicitly states a specific verb ('Prepare (not perform)') and resource ('one LinkedIn action'), and clarifies the output is a preview, proposal_id, and digest. It clearly distinguishes itself from the sibling linkedin_execute_action by emphasizing non-execution and the need to preview first.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit when-to-use context: 'Always show the user the exact preview before calling linkedin_execute_action.' This directly names the alternative tool and the condition that selects it, leaving no ambiguity about the workflow.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
linkedin_statusARead-only
Show the connected LinkedIn member, token expiry, today's write usage, the approval mode, and what this server can and cannot do. Never writes to LinkedIn.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, and the description reinforces this with 'Never writes to LinkedIn.' It goes beyond the annotations by disclosing what can be observed (token expiry, today's write usage, approval mode) and that the tool itself defines the server's capability limits — useful behavioral context for a status probe.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One sentence enumerating outputs plus a four-word safety clause. Front-loaded with the resource being shown, zero filler, and the read-only guarantee is placed where it matters most.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the burden of describing return content, and it enumerates the key fields well. The phrase 'what this server can and cannot do' remains vague about the actual shape of that data, but for a zero-parameter read-only status tool this is close to complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline expectation is a 4. The description appropriately does not invent parameter semantics and instead spends its words on output content, which is the relevant information for a no-arg status call.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Show') and enumerates the exact resources returned: connected member, token expiry, today's write usage, approval mode, and server capability boundaries. The closing 'Never writes to LinkedIn' implicitly distinguishes it from the write-oriented siblings linkedin_prepare_action and linkedin_execute_action.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: an agent can infer this is the introspection call for connection/session state before invoking the action siblings, but the description never states when to call it versus linkedin_list_actions or the action tools, nor any prerequisites. Adequate but leaves routing to inference.
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.
5 tool updates
v0.1.0- First observed
linkedin_cancel_action - First observed
linkedin_execute_action - First observed
linkedin_list_actions - First observed
linkedin_prepare_action - First observed
linkedin_status
TDQS
Scored across 5 tools
Each tool maps to a distinct phase of the action lifecycle (status, prepare, execute, list, cancel) with no overlapping responsibilities. Descriptions explicitly clarify what each does and does not do, making selection unambiguous.
All tools share the linkedin_ prefix and snake_case, with four following a verb_noun pattern. linkedin_status is a noun-only outlier, but overall naming is consistent and readable.
Five tools fully cover the propose-approve-execute-cancel cycle without redundancy. The count is well-scoped for the focused purpose of managing LinkedIn write actions with approval.
The surface covers preparation, execution, cancellation, listing, and status for write actions. Missing an update/edit action and broader read operations, but the approval workflow itself has no dead ends.
Maintenance
Related MCP Connectors
Schedule and publish LinkedIn posts to profiles and company pages from AI agents.
LinkedIn for AI agents: inbox, invitations, Sales Navigator search, posts, jobs, quotas, webhooks.
Draft LinkedIn posts in your voice, then schedule or publish them through LinkedIn's official API.
- linkedinOAuthio.reachium
LinkedIn campaigns, content, leads and inbox from your AI client. Scoped, revocable keys.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to interact with LinkedIn for posting, commenting, liking, and managing connections via OAuth2 authentication.13 npmMIT
- AlicenseBqualityCmaintenanceEnables AI agents with read/write access to LinkedIn API, including profile, posts, media, organizations, comments, reactions, and analytics.2011 npmMIT
- AlicenseAqualityCmaintenanceEnables AI agents to publish posts, images, comments, and reactions to LinkedIn as the authenticated user, with built-in safety features like daily budgets and deduplication.913 npmApache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to audit professional profiles, check policy compliance, preview posts, and publish explicitly confirmed text posts through LinkedIn's official API.MIT