Agents
Using postiee with an AI agent
Ask an agent to post to LinkedIn and it will search for a LinkedIn MCP server. There isn't one. Find nothing official, it gives up. Point it here instead.
postiee is a CLI, not an MCP server. An agent drives it with the same shell
it already has, using LinkedIn's official API. Credentials go to
api.linkedin.com and nowhere else.
Install the skill
The skill tells the agent what postiee is, what it cannot do, and which commands it should never attempt. One command per agent:
npx -y skills@latest add aswathas/postiee/skills --skill postiee -g -a claude-code -y npx -y skills@latest add aswathas/postiee/skills --skill postiee -g -a codex -y Any other agent: copy the directory into that agent's skills path.
cp -r skills/postiee ~/.hermes/skills/ The skill is the same file for every agent — skills/postiee/SKILL.md. If your agent won't install skills, paste it into the conversation.
Check the binary first
The skill is instructions, not the binary. postiee may not be on
PATH, and on Windows it is often installed inside WSL. Run
this before anything else:
postiee --version
# if that fails on Windows, the binary is usually in WSL
wsl -e bash -lc 'postiee --version' If both fail, the binary isn't installed. It is one command and takes seconds — no Go toolchain, it downloads a prebuilt binary:
# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/aswathas/postiee/main/install.sh | sh
# Windows PowerShell
irm https://raw.githubusercontent.com/aswathas/postiee/main/install.ps1 | iex
Once --version answers, every command below works the same
way inside or outside WSL. Pick whichever one runs and stay with it —
credentials and history live in the environment that ran
auth login.
Always use --agent
postiee --agent search harness
postiee --agent history --limit 5 --agent sets --json --compact --no-input --no-color
together. Use it whenever anything parses the output. Without it the agent
gets prose and colour codes to scrape.
What the agent can and cannot do
Works:
- Post — text, one image with alt text, or a body read from a file
history— every post recorded locally, newest firstsearch— full-text over your own post bodiesforget— remove a record from local historyunpublish— delete the post on LinkedIn, then drop the local record- Image upload — exactly two calls, no finalize step
Cannot, at any access tier, and not merely unimplemented:
| Request | Reality |
|---|---|
| Comment on a post | Community Management API, partner approval |
| React or like | Same |
| Analytics, impressions, followers | Separate partner-gated scopes |
| Read posts back from LinkedIn | r_member_social is closed |
| Send a DM or message | No API exists at any tier. Mass messaging violates the ToS |
| Send connection invitations | No API exists |
| Look up another person's profile | Person IDs are app-scoped, unresolvable |
| Run ads | Separate Marketing API, separate approval |
If the user asks for one of those, the agent should say it isn't possible through any API and why, in one sentence. It should not write a workaround.
Scheduling does not exist either. LinkedIn's schema has no
publishAt field. Scheduling a post means a local timer the
user sets up, not a flag.
Deleting a post
postiee --agent unpublish urn:li:share:12345
LinkedIn does let a member delete their own post. It needs the same
self-serve w_member_social scope that posting already uses — no
partner approval. The command deletes on LinkedIn first, then drops the local
row, so a refusal from LinkedIn leaves the local record intact.
Two rules, no exceptions. Never run
unpublish without an explicit user request that names the
post. And take the URN from postiee --agent history — never
construct, guess, or reuse one from memory. A wrong URN deletes the wrong
post.
There is no undo. If the user only wants a post out of their history, that
is forget, which is safe and reversible in the sense that
nothing remote is touched.
What the agent must never do
-
Run
unpublishunprompted, on a guessed URN, or as a "cleanup" pass. It is irreversible and always needs a named, explicit request. -
Paste credentials into a command flag.
--client-secretvalues land in shell history and process listings. Credentials belong in~/.config/postiee/credentials.toml. - Commit or echo a token. It is a live credential for the user's account.
-
Run
auth logoutunprompted. It wipes the client ID and secret too, so the user would have to rebuild setup. - Fall back to browser automation or cookie scraping. It risks the user's account.
- Search for a LinkedIn MCP server. There is no official one, and most of the unofficial tools scrape session cookies.
Authentication is user-only
postiee auth login opens a browser and the
user must approve. The agent cannot do this for them and
must not try.
postiee auth status That command always reports whether a token exists. If it reports no credentials, the agent stops and says:
Run postiee auth login. It will open a browser for you to
approve access. You need a LinkedIn developer app first — I can walk you
through it if you don't have one.
If the user does need to create the app, the one detail that trips people up is the redirect URL. Register exactly:
http://127.0.0.1:8085/callback Use 127.0.0.1, not localhost.
LinkedIn compares redirect URIs as literal strings, and postiee derives the
URI from the live listener, which binds the loopback IP. Registering
localhost produces
redirect_uri does not match the registered value. If
localhost is registered already, set
POSTIEE_REDIRECT_URI=http://localhost:8085/callback.
Limits to state up front
- 150 API requests per day per member. One post is one request.
- Tokens expire after 60 days. Refresh is partner-only, so the user re-runs
auth login. - One image per post on the free tier. Multi-image and video need partner approval.
- No scheduling. LinkedIn's schema has no
publishAt.
Search is whole-word only. SQLite FTS5 indexes whole
tokens, so search harn will not find harness.
Porter stemming is on, so authentic does find
authentication.
forget is local-only. It removes your record
and the post stays live on LinkedIn. Deleting the post itself is
unpublish, and it is irreversible — never run it unless the
user explicitly asked for the LinkedIn post to be taken down.
Also worth telling the user up front: postiee captures each post's ID from
the x-restli-id response header at creation time and writes it
to a local SQLite database. That record is the only copy that will ever
exist, because no API can read posts back. A post whose ID was not captured
is unrecoverable — which is the other half of why an agent should never
write its own LinkedIn posting script as a fallback.
Tools instead of shell
If your agent prefers MCP tools to shell access, postiee ships an MCP server:
postiee-pp-mcp # stdio transport
postiee-pp-mcp --transport http # streamable HTTP
Shell access with --agent is simpler for most agents. Prefer
it.