← back to Linkedin Agent

docs/setup.md

92 lines

# Setup walkthrough

Estimated time: 30 min from "no LinkedIn dev app" to "first post via Claude Code MCP."

## 1. Register a LinkedIn developer app (10 min)

1. Go to https://developer.linkedin.com/ → "My apps" → "Create app".
2. Fill out the form:
   - **App name:** `Steve Personal Agent` (or whatever — only you see this)
   - **Company page:** select your existing Designer Wallcoverings page, or create a new throwaway company you control
   - **App logo:** any 100×100 PNG (LinkedIn requires one)
   - **Legal agreement:** check the box
3. After creation, in the **Auth** tab:
   - Add redirect URL: `http://localhost:7480/api/auth/linkedin/callback`
   - Note the **Client ID** + **Client Secret** (Secret only displayed once — copy now)
4. In the **Products** tab, request access to:
   - **Sign In with LinkedIn using OpenID Connect** (instant approval)
   - **Share on LinkedIn** (instant approval)
   - **Marketing Developer Platform** (manual review — usually 3-7 days, only needed for Company Page posts and lead gen)

## 2. Route credentials via /secrets (2 min)

Per Steve's standing rule, paste both values via the secrets-manager skill:

```
LINKEDIN_CLIENT_ID=<value>
LINKEDIN_CLIENT_SECRET=<value>
```

The skill will fan them to `~/Projects/linkedin-agent/.env.local` automatically.

## 3. Install + boot (5 min)

```sh
cd ~/Projects/linkedin-agent
npm install
cp .env.local.example .env.local  # if it doesn't exist after secrets-manager
# Set AUTH_PASSWORD + SESSION_SECRET in .env.local:
echo "AUTH_PASSWORD=$(openssl rand -hex 12)" >> .env.local
echo "SESSION_SECRET=$(openssl rand -hex 32)" >> .env.local
npm run dev
```

Server boots on http://localhost:7480.

## 4. First OAuth flow (2 min)

1. Open http://localhost:7480/auth in your browser.
2. LinkedIn login screen → grant permissions.
3. You'll land on `/api/auth/linkedin/callback?code=...` and see a success page.
4. Token written to `.env.local` (`LINKEDIN_ACCESS_TOKEN`, `LINKEDIN_REFRESH_TOKEN`, `LINKEDIN_TOKEN_EXPIRES_AT`).

## 5. Smoke-test a post (1 min)

```sh
curl -X POST http://localhost:7480/api/post \
  -H "Content-Type: application/json" \
  -H "X-Admin-Key: $(grep AUTH_PASSWORD .env.local | cut -d= -f2)" \
  -d '{"text":"Testing my new LinkedIn agent. If you see this, the OAuth flow worked. Building lately: Grant, Patty, Norma."}'
```

Should return `{"ok": true, "id": "urn:li:share:..."}`.

Refresh your LinkedIn feed. The post should be there.

## 6. Wire MCP into Claude Code (5 min)

See [`mcp-config.md`](./mcp-config.md).

## 7. (Optional) Run as pm2 (1 min)

```sh
cd ~/Projects/linkedin-agent
pm2 start ecosystem.config.js
pm2 save
```

Now `linkedin-agent` boots on Mac restart.

## Token refresh

LinkedIn access tokens last 60 days. Refresh tokens last 365 days. The
agent doesn't auto-refresh yet (tracked in TODO). For now, when you get a
401 from a post call, re-run the OAuth flow at `/auth`.

## Troubleshooting

- **"unauthorized_scope_error"** — your app doesn't have the right Products approved. Go back to the Products tab and add Sign In with LinkedIn + Share on LinkedIn.
- **"redirect_uri_mismatch"** — the redirect URI in your app settings must EXACTLY match `LINKEDIN_REDIRECT_URI` in `.env.local`. Trailing slashes count.
- **Posts return 422** — usually means LinkedIn rejected the post body. Check for over-3000-char text, broken Unicode, or restricted hashtags.
- **Connection requests** — not supported by the API. Use Browserbase mode (see README) or do them manually.