Skip to content
SocialsForAI
Menu

LinkedIn MCP server

A LinkedIn MCP server your agent can't embarrass you with.

Hosted, so there's nothing to run. The agent drafts, dry runs against LinkedIn's rules, waits for your yes, and publishes to your profile.

The short answer · Updated Nov 1, 2026

A LinkedIn MCP server exposes LinkedIn posting as tools an AI agent can call over the Model Context Protocol. SocialsForAI hosts one at https://socialsforai.com/api/mcp (Streamable HTTP, bearer API key). It posts to personal LinkedIn profiles through LinkedIn's Posts API, checks the 3,000-character limit before anything is sent, escapes LinkedIn's reserved characters, and returns the post URL for each account. Company Pages aren't supported.

LinkedIn: live. Posting to LinkedIn works on the API today.

What a LinkedIn MCP server does

To let Claude or Cursor post to LinkedIn, something has to hold an OAuth token with the w_member_social permission, format text the way LinkedIn's API expects, upload images, and report back. Our server does that work and exposes it as eight tools. The agent never sees the token; it sees account ids like acc_Li4n....

Under the hood, create_post sends POST https://api.linkedin.com/rest/posts with the Linkedin-Version and X-Restli-Protocol-Version: 2.0.0 headers LinkedIn requires, uploads images through LinkedIn's Images API first, and reads the new post's id from the x-restli-id response header. You don't need your own LinkedIn developer app. For the raw API details see our LinkedIn API guide; for the protocol side see the MCP server overview.

The tools that matter for LinkedIn

All eight tools work the same on every network, and their full inputs are listed once in every tool and its inputs. Here is what five of them say or do when the account is a LinkedIn profile.

  • list_platforms: the LinkedIn row carries maxChars: 3000, maxImages: 20, supportsVideo: false and dailyPostCap: 50. Its available flag is false on the hosted server until our production LinkedIn app is configured, so an agent that reads it first won't offer LinkedIn before it works.
  • list_accounts with platform: "linkedin": LinkedIn's userinfo endpoint gives us your member id, name and picture but no public handle, so handle comes back empty and display_name is the name on your profile. Tell the agent to pick the account by display_name or by id.
  • create_post: write plain text up to 3,000 characters, counted per grapheme. The dry-run preview shows your text unescaped; the backslashes LinkedIn's format needs are added only in the request to LinkedIn. One image becomes a single-image post and two to twenty become a MultiImage post, each with the alt you passed as its alt text.
  • get_post: a LinkedIn target's url is https://www.linkedin.com/feed/update/urn:li:share:<id> and external_post_id is the URN itself. verified stays false, because reading the post back needs r_member_social; the 201 from LinkedIn is the confirmation.
  • cancel_post: scheduled LinkedIn posts are held on our side, so you can cancel any time before publishing starts. After that the answer is post_not_cancelable, and the fix tells you to delete the post on LinkedIn itself.

A worked session: dry run, fix, publish

Here is what the agent sees when you ask it to repost a job ad that went out yesterday. The dry run catches the repeat before LinkedIn does. The agent rewrites, dry runs again, shows you the new text, and publishes once you approve. The publish call returns the post with one target per account. verified stays false on LinkedIn: reading a post back needs r_member_social, which LinkedIn has closed, so LinkedIn's 201 with a post id in x-restli-id is the confirmation. Ids and timestamps here are illustrative, and the shapes are the server's real responses, trimmed with ....

Notice the parentheses and brackets in the text. LinkedIn's "little" text format treats them as markup, so we send them escaped and the post reads as written.

text
// 1. Dry run the draft (the same text went out yesterday)
create_post({ "accounts": ["acc_Li4nKd8Qp2Zr7TmX"],
              "text": "We're hiring a staff engineer (remote, US). Details: ...",
              "dry_run": true })
→ { "object": "preflight", "ok": false,
    "issues": [{ "account_id": "acc_Li4nKd8Qp2Zr7TmX", "platform": "linkedin", "severity": "error",
                 "code": "duplicate_content",
                 "message": "The same text was already posted to acc_Li4nKd8Qp2Zr7TmX in the last 24 hours; platforms reject or penalize duplicates.",
                 "fix": "Vary the text for this account (platform_options) or wait 24 hours." }],
    "preview": [{ "account_id": "acc_Li4nKd8Qp2Zr7TmX", "platform": "linkedin",
                  "text": "We're hiring a staff engineer (remote, US). Details: ..." }] }

// 2. Rewrite, dry run again → { "object": "preflight", "ok": true, "issues": [], ... }

// 3. Publish (after the human approves)
create_post({ "accounts": ["acc_Li4nKd8Qp2Zr7TmX"],
              "text": "Still hiring: staff engineer [remote, US]. What the first 90 days look like: ...",
              "idempotency_key": "staff-eng-li-2" })
→ { "id": "post_Hq7Tn2Wd5Ks9Lp3R", "object": "post", "status": "published",
    "targets": [{ "account_id": "acc_Li4nKd8Qp2Zr7TmX", "platform": "linkedin", "status": "published",
                  "url": "https://www.linkedin.com/feed/update/urn:li:share:<id>",
                  "external_post_id": "urn:li:share:<id>", "attempts": 1,
                  "verified": false, "error": null, ... }], ... }

LinkedIn rules the server handles

These come from LinkedIn's own documentation and from how our LinkedIn provider is written. The agent doesn't have to know them; the dry run tells it when it breaks one.

  • Length: LinkedIn Help says posts can be up to 3,000 characters. Preflight counts the final text for each account and returns text_too_long with the overage.
  • Reserved characters: LinkedIn's Posts API stores commentary in the little text format, where | { } @ [ ] ( ) < > # \ * _ ~ are reserved and must be escaped with a backslash to show as plain text. We escape all of them except #, so hashtags still work. Without escaping, a post with "(remote)" or "@team" can be cut off or misparsed.
  • Images: up to 20 per post, 20 MB each, uploaded through LinkedIn's Images API. More than one image becomes a MultiImage post, which LinkedIn supports for organic posts. Organic carousels aren't supported by LinkedIn.
  • Video: not yet supported on any network. A video item returns video_not_supported.
  • Posting volume: LinkedIn says it may limit how many posts a member can make in a period without publishing a number. Our fair-use cap is 50 posts per LinkedIn account per rolling 24 hours.
  • Tokens: LinkedIn member tokens last 60 days, and refresh tokens need partner access we don't have. When a token expires the account moves to needs_reauth and the agent gets account_needs_reauth with a fix.

Before an agent posts to LinkedIn for you

A LinkedIn post goes out under your own name, with PUBLIC visibility and main-feed distribution, to the people you work with and the ones who might hire you. That audience is less forgiving of a stray draft than most, so set the agent up to ask first.

  • Make the preview the default. Tell the agent that every LinkedIn post starts as create_post with dry_run: true, and that it shows you the preview text before the real call. The dry run is plain code with no AI calls and no cost, and it doesn't use up the idempotency key.
  • Keep the publish behind a click. Leave your client's approval prompt on for create_post. A published LinkedIn post can't be pulled back through our API; only scheduled ones can be canceled.
  • Fifty a day is a ceiling, not a target. Our cap is 50 posts per profile per rolling 24 hours (daily_cap_reached), and LinkedIn may limit members sooner without saying where. A person posting a few times a week is far below either.
  • Personal profile only. If you ask the agent to post as your company, the right answer is that it can't: Company Pages need w_organization_social, which we don't have. It should not fall back to your own profile.
  • Plan for the 60-day token. When it runs out, publishing stops with account_needs_reauth and the account.needs_reauth webhook fires. Subscribe to it if you want a message the day it happens rather than when the agent next tries.
  • Retry with the same key. If the agent times out mid-publish and calls again with the same idempotency_key and body, it gets the first post back instead of a second copy.

Set up for LinkedIn: add the server, then approve on LinkedIn

Add the server once. In Claude Code the command below registers it for this project; the configs for Cursor and Claude Desktop, and notes on scope, are in client setup.

Then connect the profile. Ask the agent to call create_connect_link with platforms: ["linkedin"]. The link sends you to LinkedIn's authorization page at linkedin.com/oauth/v2/authorization, asking for openid profile w_member_social. After you allow it, we exchange the code for a token, read your member id from LinkedIn's userinfo endpoint, and store the token encrypted with AES-256-GCM. If the link was created with a redirect_url, you land there with ?account_id=...&platform=linkedin&status=connected.

bash
export SOCIALS_API_KEY=sk_...
claude mcp add --transport http socials https://socialsforai.com/api/mcp \
  --header "Authorization: Bearer $SOCIALS_API_KEY"

LinkedIn errors and their fixes

These are the codes you'll see on LinkedIn targets specifically. Codes that work the same on every network, such as a 401 at connect time, account_not_found or idempotency_key_reused, are in the full troubleshooting list.

  • account_needs_reauth on LinkedIn usually means the 60-day token ran out. Ask the agent for create_connect_link with platforms: ["linkedin"] and approve again on LinkedIn's OAuth screen. The account keeps its id.
  • platform_rejected with a LinkedIn message such as FIELD_LENGTH_TOO_LONG: LinkedIn refused the request after preflight passed. The fix field says to adjust the post and retry with a new idempotency key.
  • platform_rate_limited: LinkedIn answered 429 TOO_MANY_REQUESTS. It's retryable, so we try again on our schedule; check back with get_post rather than sending the post again.
  • too_many_media or media_too_large: more than 20 images, or one over 20 MB. Drop images or compress them and pass new URLs.
  • duplicate_content: the same text reached this profile in the last 24 hours, or LinkedIn's own response called it a duplicate. Rewrite it; for a repost, change the opening line.
  • platform_unavailable from create_connect_link, or platform_not_configured in a dry run: LinkedIn isn't enabled on the server you're calling. On the hosted server it's live; list_platforms shows the current status.

Status: what works today

LinkedIn posting to personal profiles is live on the hosted server: OAuth with openid profile w_member_social, text, images, escaping, and a receipt that reports verified: false because w_member_social can't read posts back and LinkedIn keeps r_member_social closed. Company Pages need w_organization_social and LinkedIn's approval, which we don't have, so they aren't supported. X, Bluesky, Mastodon and Telegram are live too; see the X MCP server and Bluesky MCP server.

Connect LinkedIn to your agent in five steps

  1. 01

    Create a workspace key

    Sign up or call POST /v1/workspaces. The key starts with sk_ and the workspace starts on Free, which has room for a LinkedIn profile and two more accounts.

  2. 02

    Register the server

    Add the hosted URL to your MCP client with the key as a bearer header. The Claude Code command is below; Cursor and Claude Desktop configs are on the MCP hub.

  3. 03

    Approve LinkedIn's consent screen

    Ask for a LinkedIn connect link. Open it signed in to the profile that should post and allow the openid, profile and w_member_social permissions.

  4. 04

    Dry run with the real wording

    Have the agent dry run the exact post. Parentheses, @ signs and brackets stay as written; check the preview for length and for anything you'd rather not say under your name.

  5. 05

    Publish and keep the URL

    Approve the publish. The LinkedIn target returns a feed/update URL with the post's URN. Expect to reconnect in about 60 days.

Questions

What developers ask

Do I have to run a LinkedIn MCP server myself?

No. Ours is hosted at /api/mcp. You add one URL and an API key to your client; we hold the LinkedIn app, the tokens and the formatting rules.

Do I need my own LinkedIn developer app?

No. You approve access through our app on LinkedIn's own OAuth screen, from a connect link. The agent never sees your password or token.

Can the agent post to my Company Page?

No. Company Page posting needs the w_organization_social permission and LinkedIn's approval, which we don't have. Personal profiles only.

Why do my parentheses and @ signs disappear when I post to LinkedIn through the API?

LinkedIn's little text format reserves them as markup. They must be escaped with a backslash. Our server escapes them for you, leaving hashtags working.

How often will I have to reconnect LinkedIn?

About every 60 days, when LinkedIn's member token expires. The agent gets account_needs_reauth and can create a connect link for you.

Why is verified false on my LinkedIn post?

Reading a post back needs the r_member_social permission, which LinkedIn restricts. We treat LinkedIn's 201 response with the post id as the confirmation, and the post URL is still returned.

What does the LinkedIn MCP server cost?

Nothing extra per post. You pay per account that posted in the month; Free includes 3 of them and 100 posts. See pricing.

Get an API key and send your first post in five minutes.