linkedin-mcp
by Hoyasumii
README.md
<div align="center">
# @hoyasumii/linkedin
**An unofficial MCP server and TypeScript SDK to create, edit and delete your own LinkedIn posts.**
[](https://www.npmjs.com/package/@hoyasumii/linkedin)
[](https://github.com/Hoyasumii/linkedin/actions/workflows/cd.yml)
[](https://hoyasumii.github.io/linkedin/)
[](https://orval.dev)
[](spec/openapi.yml)
[](https://nodejs.org)
[](#windows)
[](LICENSE)
</div>
**Documentation: [hoyasumii.github.io/linkedin](https://hoyasumii.github.io/linkedin/)** (English and Português),
also as [`llms.txt`](https://hoyasumii.github.io/linkedin/llms.txt) and
[`llms-full.txt`](https://hoyasumii.github.io/linkedin/llms-full.txt) for LLMs. `linkedin docs` opens it.
| Page | What it covers |
| ----------------------------------------------------------------------------------- | --------------------------------------------------------- |
| [Getting started](https://hoyasumii.github.io/linkedin/docs/intro) | what LinkedIn allows, installation, a first post |
| [Creating the LinkedIn app](https://hoyasumii.github.io/linkedin/docs/linkedin-app) | the one-time setup on linkedin.com/developers |
| [MCP server](https://hoyasumii.github.io/linkedin/docs/mcp/overview) | setup, every tool, configuration, programmatic use |
| [SDK](https://hoyasumii.github.io/linkedin/docs/sdk/overview) | the client, posts, media, errors |
| [CLI](https://hoyasumii.github.io/linkedin/docs/cli/overview) | `linkedin mcp config/install/status/logout`, Windows, WSL |
| [API reference](https://hoyasumii.github.io/linkedin/docs/api) | every exported class, type and function |
Unlike its siblings [`@hoyasumii/plane`](https://github.com/Hoyasumii/plane),
[`@hoyasumii/signoz`](https://github.com/Hoyasumii/signoz) and
[`@hoyasumii/libretranslate`](https://github.com/Hoyasumii/libretranslate), this package delivers an **MCP server
and an SDK**. Its CLI only signs you in and registers the server in your AI clients.
1. **MCP server** (`@hoyasumii/linkedin/mcp`, bin `linkedin-mcp`, stdio): `linkedin_create_post`,
`linkedin_edit_post`, `linkedin_delete_post`, `linkedin_list_posts`, `linkedin_get_post` and `linkedin_whoami`.
2. **SDK** (`createLinkedInClient`): posts with text, images (1 to 20), a video (uploaded in parts), a document or an
article link; edit the text; delete. Generated by [orval](https://orval.dev) from an OpenAPI 3.1 spec written
for this package, with LinkedIn's _little_ text format escaped for you.
3. **CLI** (bin `linkedin`): `linkedin mcp config --web` signs you in to LinkedIn from a local page;
`linkedin mcp install` registers the server in Claude Code, Codex or OpenCode.
> [!IMPORTANT]
> LinkedIn lets any app **write** a member's posts, but not **read** them: `r_member_social` is closed to new
> apps. The server keeps a local list of the posts it created or edited, and its "read" tools show that list.
> Posts made on linkedin.com can still be edited or deleted by their URN or URL.
> [!NOTE]
> An independent, **unofficial** client of LinkedIn's official API, MIT licensed. Not affiliated with or endorsed by
> LinkedIn Corporation. It posts only through an app you create and authorize, as you.
## Installation
```sh
npm i -g @hoyasumii/linkedin # or: pnpm add -g, or clone + pnpm build + npm i -g .
```
Create [your LinkedIn app](https://hoyasumii.github.io/linkedin/docs/linkedin-app) once (a Company Page, the
**Share on LinkedIn** and **Sign In with LinkedIn using OpenID Connect** products, and the redirect URL
`http://localhost:3769/callback`), then:
```sh
linkedin mcp config --web # paste the Client ID and Secret, sign in on LinkedIn
linkedin mcp install # register linkedin-mcp in Claude Code / Codex / OpenCode
```
The token lasts 60 days: run `linkedin mcp config --web` again when `linkedin mcp status` says it expired.
## SDK
```ts
import { createLinkedInClient } from "@hoyasumii/linkedin";
const linkedin = createLinkedInClient({ accessToken: process.env.LINKEDIN_ACCESS_TOKEN! });
const { urn, url } = await linkedin.createPost({ text: "Shipped v1.0 (finally) #release" });
await linkedin.createPost({ text: "Screens", images: ["a.png", { file: "b.png", altText: "The dashboard" }] });
await linkedin.createPost({ text: "Demo", video: { file: "demo.mp4", title: "Demo" }, visibility: "CONNECTIONS" });
await linkedin.createPost({ text: "Our roadmap", document: "roadmap.pdf" });
await linkedin.editPost(urn, { text: "Shipped v1.0.1" });
await linkedin.deletePost(url);
```
A non-2xx answer throws `LinkedInApiError` (`status`, `code`, `serviceErrorCode`, `body`); an expired or missing
sign-in throws `LinkedInAuthError`. The token is never in an error.
## MCP server
| Tool | What it does |
| ---------------------- | ----------------------------------------------------------------------- |
| `linkedin_create_post` | Text, plus images, a video, a document or an article link; or a reshare |
| `linkedin_edit_post` | Replaces a post's text (LinkedIn does not let media change) |
| `linkedin_delete_post` | Deletes a post, with `confirm: true` |
| `linkedin_list_posts` | The posts made or edited through the server (local list) |
| `linkedin_get_post` | One of them |
| `linkedin_whoami` | Who is signed in, and for how many more days |
By hand: `claude mcp add linkedin -s user -- npx -y -p @hoyasumii/linkedin linkedin-mcp`. The server reads the
saved sign-in on every call; `LINKEDIN_ACCESS_TOKEN` in its environment overrides it.
## Windows
Native Windows 10/11 (PowerShell, cmd) and WSL. From WSL, `linkedin mcp install` also reaches the clients
installed on the Windows side. See [Windows and WSL](https://hoyasumii.github.io/linkedin/docs/cli/windows).
## Development
```sh
pnpm install
pnpm codegen # spec/openapi.yml → src/generated/ (orval)
pnpm test:unit # a fake LinkedIn on node:http, no network
pnpm test:live # posts, edits and deletes on the real LinkedIn (.env.test, see env.example)
pnpm docs:dev # the docs site
```
See [CONTRIBUTING.md](CONTRIBUTING.md).
## Releasing
Every push to `main` runs [`.github/workflows/cd.yml`](.github/workflows/cd.yml): lint, format, types, knip, unit
tests and the build. Then:
- when `package.json`'s `version` is not on npm yet, it publishes it with provenance through
[npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers) (OIDC, no token in the repository), tags it
`v<version>` and opens a GitHub release;
- when the push touches `website/` or `src/`, it rebuilds the docs site and deploys it to the `gh-pages` branch.
```sh
# bump "version" in package.json, then:
git commit -am "chore: release 0.1.1"
git push origin main
```
## License
[MIT](LICENSE) © Alan Reis Anjos