← 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.