← back to Linkedin Agent
README.md
143 lines
# LinkedIn Agent
Steve's autonomous LinkedIn presence — posts, comments, connection requests,
Company Page management, AI-drafted content — all wired into Claude Code via
an MCP server.
**Port:** 7480 (Express OAuth+API server) · MCP: stdio (no port)
**Stack:** Next.js 16 / Express / TypeScript / `@modelcontextprotocol/sdk`
**LinkedIn auth:** OAuth 2.0 + 3-legged flow (user-context tokens)
---
## What it does (and what it can't)
### ✅ Real LinkedIn API support
- Post text + image + video ("UGC posts" / "shares" via `/v2/ugcPosts`)
- Comment on posts (`/v2/socialActions/{urn}/comments`)
- Read your own profile + connection list
- Manage Company Pages: post on behalf of, fetch analytics
- React (👍 ❤️ 💡 etc.) on posts
### ⚠️ Requires LinkedIn Marketing Developer Program approval
- Lead Gen Forms · Audience targeting · Sponsored content · Analytics
### ❌ NOT supported by LinkedIn API (use Browserbase fallback)
- **Sending connection requests at scale** — LinkedIn officially does not expose this. Doing it via browser automation puts the account at risk of restrictions.
- **Bulk people search beyond first-degree network** — Sales Navigator API is enterprise-only.
- **DM at scale** — same restrictions as connection requests.
For the unsupported flows, this agent has a `browserbase-mode` that uses
the Browserbase MCP (per Steve's "Browserbase before any other headless
browser" rule) with a logged-in LinkedIn session cookie. Slower, riskier,
but functional. Off by default.
---
## Always-include "Steve's skills" promo
Every generated post automatically appends/weaves in Steve's current skill
& agent inventory from `templates/STEVE_BIO.md`. The point is to keep
LinkedIn aware of every product/tool/agent Steve ships, in his own words,
in real time. The bio file is the single source of truth — update it once
and every future post reflects.
Template snippets in `templates/`:
- `STEVE_BIO.md` — current skills + active agents (refresh weekly)
- `post-skill-launch.md` — "I just shipped X" post template
- `post-thought-leadership.md` — opinion + insight format
- `post-product-update.md` — "Designer Wallcoverings now does Y" format
- `post-agent-spotlight.md` — featuring one of Steve's autonomous agents
- `comment-helpful.md` — substantive comment seeds (not "great post!")
- `connect-message.md` — personalized connection-request opener
---
## Setup (one-time)
1. **Register a LinkedIn developer app** → https://developer.linkedin.com/
- Pick scopes: `r_liteprofile r_emailaddress w_member_social w_organization_social rw_organization_admin`
- Add redirect URI: `http://localhost:7480/api/auth/linkedin/callback`
2. **Set env vars** in `.env.local`:
```
LINKEDIN_CLIENT_ID=<your client id>
LINKEDIN_CLIENT_SECRET=<route via /secrets, never commit>
LINKEDIN_REDIRECT_URI=http://localhost:7480/api/auth/linkedin/callback
LINKEDIN_ORG_URN=urn:li:organization:<your-company-id> # optional, for Page posts
AUTH_PASSWORD=<route via /secrets>
SESSION_SECRET=<openssl rand -hex 32>
```
3. **Install + first auth**:
```
npm install
npm run dev
# open http://localhost:7480/auth → "Sign in with LinkedIn" → grants stored to .env.local
```
4. **Wire MCP into Claude Code**: see `docs/mcp-config.md`
---
## What you can ask Claude after MCP is wired
- "Post a LinkedIn announcement about my new Grant project — make it lead-gen-y"
- "Find 10 grant-writers in LA and draft connection requests"
- "Comment thoughtfully on the last 3 posts from @InstrumentlGrants"
- "Schedule a daily 9am post about whichever DW agent shipped that day"
- "Pull analytics on my last 30 days — engagement by post type"
---
## Standing rules honored
- No secrets in argv (`feedback_no_secret_interpolation_in_bash.md`)
- LinkedIn credentials route via `/secrets` skill (`feedback_secrets_auto_route.md`)
- Browserbase before Playwright/Puppeteer (`feedback_browserbase_first.md`)
- Email/comms compliance: every outbound message routes through the
`comms-compliance` subagent first when it's a cold connection request
---
## File map
```
linkedin-agent/
├── README.md ← this file
├── package.json
├── .env.local.example
├── ecosystem.config.js ← pm2 config (port 7480, name linkedin-agent)
├── src/
│ ├── server.ts ← Express OAuth + REST API
│ ├── lib/
│ │ ├── linkedin.ts ← LinkedIn v2 API wrapper
│ │ ├── auth.ts ← OAuth 2.0 token store
│ │ ├── browserbase.ts ← fallback for unsupported flows
│ │ └── compose.ts ← merges STEVE_BIO into post drafts
│ └── mcp/
│ └── server.ts ← MCP stdio server (tools: post/comment/etc)
├── templates/
│ ├── STEVE_BIO.md ← always-included skills/agents bio
│ ├── post-skill-launch.md
│ ├── post-thought-leadership.md
│ ├── post-product-update.md
│ ├── post-agent-spotlight.md
│ ├── comment-helpful.md
│ └── connect-message.md
└── docs/
├── setup.md ← step-by-step LinkedIn dev portal walk
├── mcp-config.md ← Claude Code MCP wiring
└── compliance.md ← what NOT to do (LinkedIn ToS)
```
---
## Status
**v0 scaffold landed 2026-05-06 (tick 53)** — files + docs + templates
present, no live LinkedIn API calls yet. Next steps require Steve:
1. Register dev app + provide Client ID/Secret
2. Run `npm install` + first OAuth flow
3. Author `templates/STEVE_BIO.md` with current skills inventory
Once #1-#3 done, agent goes live. ETA from "Steve has Client ID" to "first
post via Claude Code MCP" = ~30 min.