k-linkedin-mcp 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitlab-ci.yml +36 -53
- package/CHANGELOG.md +30 -1
- package/Dockerfile +1 -0
- package/PRIVACY.md +36 -0
- package/README.md +116 -38
- package/config.json +2 -2
- package/package.json +1 -1
- package/src/.env.example +2 -2
- package/src/admin/cli.js +193 -25
- package/src/admin/oauth.js +18 -14
- package/src/admin/service.js +262 -44
- package/src/admin/telegram.js +87 -26
- package/src/config.js +91 -30
- package/src/http_app.js +80 -8
- package/tests/admin_service.test.js +136 -1
- package/tests/telegram.test.js +89 -7
package/.gitlab-ci.yml
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
# linkedin-mcp — GitLab CI/CD Pipeline
|
|
2
|
-
#
|
|
2
|
+
# test: shared runner (bun image)
|
|
3
|
+
# deploy: homelab runner (runs directly on the server — no SSH needed)
|
|
3
4
|
|
|
4
5
|
stages:
|
|
5
6
|
- test
|
|
6
|
-
- build
|
|
7
7
|
- deploy
|
|
8
8
|
|
|
9
|
-
variables:
|
|
10
|
-
IMAGE_NAME: registry.gitlab.com/kpihx-labs/linkedin-mcp
|
|
11
|
-
CONTAINER_NAME: linkedin_mcp
|
|
12
|
-
|
|
13
9
|
# ── Test ──────────────────────────────────────────────────────────────────────
|
|
14
10
|
|
|
15
11
|
test:
|
|
@@ -18,61 +14,48 @@ test:
|
|
|
18
14
|
script:
|
|
19
15
|
- bun install --frozen-lockfile
|
|
20
16
|
- bun test
|
|
21
|
-
coverage: '/\d+ pass/'
|
|
22
|
-
artifacts:
|
|
23
|
-
reports:
|
|
24
|
-
junit: test-results.xml
|
|
25
|
-
when: always
|
|
26
|
-
expire_in: 7 days
|
|
27
17
|
rules:
|
|
28
18
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
29
19
|
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
30
20
|
- if: '$CI_COMMIT_BRANCH == "develop"'
|
|
31
21
|
|
|
32
|
-
# ── Build Docker image ────────────────────────────────────────────────────────
|
|
33
|
-
|
|
34
|
-
build:
|
|
35
|
-
stage: build
|
|
36
|
-
image: docker:27
|
|
37
|
-
services:
|
|
38
|
-
- docker:27-dind
|
|
39
|
-
before_script:
|
|
40
|
-
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
|
|
41
|
-
script:
|
|
42
|
-
- docker build -t $IMAGE_NAME:$CI_COMMIT_SHORT_SHA -t $IMAGE_NAME:latest .
|
|
43
|
-
- docker push $IMAGE_NAME:$CI_COMMIT_SHORT_SHA
|
|
44
|
-
- docker push $IMAGE_NAME:latest
|
|
45
|
-
rules:
|
|
46
|
-
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
47
|
-
|
|
48
22
|
# ── Deploy to Homelab ─────────────────────────────────────────────────────────
|
|
49
23
|
|
|
50
24
|
deploy:
|
|
51
25
|
stage: deploy
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
-
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
- ssh-keyscan -H $HOMELAB_HOST >> ~/.ssh/known_hosts
|
|
26
|
+
tags:
|
|
27
|
+
- homelab
|
|
28
|
+
only:
|
|
29
|
+
- main
|
|
30
|
+
needs:
|
|
31
|
+
- test
|
|
59
32
|
script:
|
|
33
|
+
- echo "Deploying linkedin-mcp to the homelab..."
|
|
34
|
+
- cd deploy
|
|
35
|
+
- echo "LINKEDIN_CLIENT_ID=$LINKEDIN_CLIENT_ID" > .env
|
|
36
|
+
- echo "LINKEDIN_CLIENT_SECRET=$LINKEDIN_CLIENT_SECRET" >> .env
|
|
37
|
+
- echo "TELEGRAM_LINKEDIN_HOMELAB_TOKEN=$TELEGRAM_LINKEDIN_HOMELAB_TOKEN" >> .env
|
|
38
|
+
- echo "TELEGRAM_CHAT_IDS=$TELEGRAM_CHAT_IDS" >> .env
|
|
39
|
+
- echo "LINKEDIN_MCP_HTTP_HOST=0.0.0.0" >> .env
|
|
40
|
+
- echo "LINKEDIN_MCP_HTTP_PORT=8095" >> .env
|
|
41
|
+
- echo "LINKEDIN_MCP_HTTP_PATH=/mcp" >> .env
|
|
42
|
+
- echo "LINKEDIN_MCP_PUBLIC_URL=https://linkedin.kpihx-labs.com" >> .env
|
|
43
|
+
- echo "LINKEDIN_STATE_DIR=/data/state" >> .env
|
|
44
|
+
- docker compose -p linkedin-mcp config -q
|
|
45
|
+
- docker rm -f linkedin-mcp || true
|
|
46
|
+
- docker compose -p linkedin-mcp up -d --build --remove-orphans
|
|
60
47
|
- |
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
# HOMELAB_SSH_KEY — private SSH key for homelab deploy user (masked, protected)
|
|
76
|
-
# HOMELAB_HOST — homelab IP/hostname reachable from CI runner (masked)
|
|
77
|
-
# CI_REGISTRY_USER — GitLab container registry user (auto)
|
|
78
|
-
# CI_REGISTRY_PASSWORD — GitLab registry password / deploy token (masked)
|
|
48
|
+
for attempt in $(seq 1 20); do
|
|
49
|
+
status="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}starting{{end}}' linkedin-mcp 2>/dev/null || true)"
|
|
50
|
+
if [ "$status" = "healthy" ]; then
|
|
51
|
+
break
|
|
52
|
+
fi
|
|
53
|
+
if [ "$status" = "unhealthy" ]; then
|
|
54
|
+
docker logs --tail 200 linkedin-mcp
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
sleep 3
|
|
58
|
+
done
|
|
59
|
+
- docker inspect --format '{{.State.Health.Status}}' linkedin-mcp | grep -qx healthy
|
|
60
|
+
- docker exec linkedin-mcp bun -e "fetch('http://127.0.0.1:8095/health').then(r=>{if(!r.ok)process.exit(1);console.log('health OK')}).catch(()=>process.exit(1))"
|
|
61
|
+
- docker image prune -f
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
## [0.2.1] — 2026-03-22
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- [x] `setAccessToken` now async with best-effort `GET /v2/userinfo` — populates `member_id`, `name`, `email` in token.json (required for posting tools that call `_memberUrn()`)
|
|
9
|
+
- [x] Status CLI display: `State dir` instead of `Admin env file` path
|
|
10
|
+
- [x] Redirect URI dynamically computed from port override in `runOAuthFlow` (was using hardcoded `config.oauth.redirect_uri`)
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- [x] linkedin-mcp registered in Codex (`~/.codex/config.toml`), Copilot (`~/.copilot/mcp-config.json`), Vibe (`~/.vibe/config.toml`) — HTTP primary + stdio fallback
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## [0.2.0] — 2026-03-22
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- [x] Unified credential management — CLI, HTTP, Telegram: `client-id set/unset`, `client-secret set/unset`, `token set/unset`
|
|
21
|
+
- [x] Admin env file pattern (`LINKEDIN_ADMIN_ENV_FILE`, default `/data/linkedin-admin.env`) — persists credentials across restarts
|
|
22
|
+
- [x] `auth --port N` option to avoid `EADDRINUSE` (default port moved to 3001 via `config.json`)
|
|
23
|
+
- [x] OAuth callback port externalized to `config.json` (DEFAULTS remain 3000 as last-resort fallback only)
|
|
24
|
+
- [x] New HTTP POST routes: `/admin/{client-id,client-secret,token}/{set,unset}`
|
|
25
|
+
- [x] New Telegram commands: `/token_set`, `/token_unset`, `/client_id_set`, `/client_id_unset`, `/client_secret_set`, `/client_secret_unset`
|
|
26
|
+
- [x] Status table: shows `LINKEDIN_CLIENT_ID`, `LINKEDIN_CLIENT_SECRET`, `OAuth Token` — Telegram token removed
|
|
27
|
+
- [x] 61 tests (18 new) — all pass
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- [x] `TELEGRAM_LINKEDIN_TOKEN` → `TELEGRAM_LINKEDIN_HOMELAB_TOKEN` reference in HTTP status handler
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
5
34
|
## [0.1.0] — 2026-03-22
|
|
6
35
|
|
|
7
36
|
### Added
|
|
@@ -12,5 +41,5 @@
|
|
|
12
41
|
- [x] Token lifecycle management — 60-day expiry tracking, `tokenSummary` with days_left
|
|
13
42
|
- [x] Test suite — 41 tests, 5 files, 0 live API calls (mocked fetch + temp dirs)
|
|
14
43
|
- [x] Docker image (`oven/bun:1-slim`) + `deploy/docker-compose.yml` with Traefik labels
|
|
15
|
-
- [x] GitLab CI — test →
|
|
44
|
+
- [x] GitLab CI — test → deploy pipeline (homelab runner, no SSH)
|
|
16
45
|
- [x] Editable install via `bun link`
|
package/Dockerfile
CHANGED
|
@@ -16,6 +16,7 @@ RUN chmod +x /app/src/main.js /app/src/admin.js /app/src/admin/cli.js \
|
|
|
16
16
|
|
|
17
17
|
ENV NODE_ENV=production
|
|
18
18
|
ENV LINKEDIN_STATE_DIR=/data/state
|
|
19
|
+
ENV LINKEDIN_ADMIN_ENV_FILE=/data/linkedin-admin.env
|
|
19
20
|
ENV LINKEDIN_MCP_HTTP_HOST=0.0.0.0
|
|
20
21
|
ENV LINKEDIN_MCP_HTTP_PORT=8095
|
|
21
22
|
ENV LINKEDIN_MCP_HTTP_PATH=/mcp
|
package/PRIVACY.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Privacy Policy — linkedin-mcp
|
|
2
|
+
|
|
3
|
+
**Last updated: 2026-03-22**
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`linkedin-mcp` is a personal tool used exclusively by its author (KpihX / Ivann KAMDEM).
|
|
8
|
+
It is not a public service and has no end-users other than the author.
|
|
9
|
+
|
|
10
|
+
## Data collected
|
|
11
|
+
|
|
12
|
+
This application does **not** collect, store, or share any personal data from third parties.
|
|
13
|
+
|
|
14
|
+
The only data persisted is:
|
|
15
|
+
|
|
16
|
+
- **LinkedIn OAuth token** — stored locally on the author's own infrastructure (`~/.mcps/linkedin/token.json`), containing the access token, expiry date, and the author's own LinkedIn member ID, name, and email. This data is never transmitted to any third party.
|
|
17
|
+
- **Admin logs** — operational log lines (auth events, command invocations) stored locally in `~/.mcps/linkedin/linkedin-admin.log`. These logs are never transmitted externally.
|
|
18
|
+
|
|
19
|
+
## LinkedIn API usage
|
|
20
|
+
|
|
21
|
+
This application uses the official LinkedIn OAuth 2.0 API with the following scopes:
|
|
22
|
+
|
|
23
|
+
- `openid` — identity verification
|
|
24
|
+
- `profile` — read own profile
|
|
25
|
+
- `email` — read own email
|
|
26
|
+
- `w_member_social` — post, like, and comment as the authenticated member
|
|
27
|
+
|
|
28
|
+
All API calls are made exclusively on behalf of the authenticated member (the author) to their own LinkedIn account.
|
|
29
|
+
|
|
30
|
+
## Third-party services
|
|
31
|
+
|
|
32
|
+
No analytics, tracking, telemetry, or third-party SDKs are included.
|
|
33
|
+
|
|
34
|
+
## Contact
|
|
35
|
+
|
|
36
|
+
For any questions: [GitHub](https://github.com/KpihX/linkedin-mcp)
|
package/README.md
CHANGED
|
@@ -34,29 +34,30 @@ linkedin-mcp/
|
|
|
34
34
|
│ ├── http_app.js ← Express HTTP app: /health /admin/* /mcp
|
|
35
35
|
│ ├── .env.example ← required secrets template
|
|
36
36
|
│ └── admin/
|
|
37
|
-
│ ├── cli.js ← Commander CLI (auth / status / logout / logs / health / urls
|
|
38
|
-
│
|
|
39
|
-
│ ├──
|
|
40
|
-
│
|
|
37
|
+
│ ├── cli.js ← Commander CLI (auth / status / logout / logs / health / urls
|
|
38
|
+
│ │ / client-id / client-secret / token)
|
|
39
|
+
│ ├── service.js ← unified admin kernel — single source of truth for all surfaces
|
|
40
|
+
│ ├── oauth.js ← LinkedIn OAuth 2.0 + OIDC flow (configurable callback port)
|
|
41
|
+
│ └── telegram.js ← optional Telegram bot admin (15 commands)
|
|
41
42
|
├── tools/
|
|
42
43
|
│ ├── registry.js ← listTools() + callTool()
|
|
43
44
|
│ ├── guide.js ← linkedin_guide
|
|
44
45
|
│ ├── profile.js ← linkedin_get_profile, linkedin_auth_status
|
|
45
46
|
│ ├── posts.js ← linkedin_create_post, linkedin_create_image_post, linkedin_delete_post
|
|
46
47
|
│ └── social.js ← linkedin_like_post, linkedin_create_comment
|
|
47
|
-
├── tests/ ← bun test (
|
|
48
|
+
├── tests/ ← bun test (61 tests, 0 deps on live API)
|
|
48
49
|
├── deploy/
|
|
49
50
|
│ ├── docker-compose.yml
|
|
50
51
|
│ └── docker-compose.override.example.yml
|
|
51
52
|
├── Dockerfile
|
|
52
53
|
├── .gitlab-ci.yml
|
|
53
|
-
└── config.json ← non-secret defaults (port 8095, state dir, scopes)
|
|
54
|
+
└── config.json ← non-secret defaults (port 8095, OAuth port 3001, state dir, scopes)
|
|
54
55
|
```
|
|
55
56
|
|
|
56
57
|
**Transport strategy:**
|
|
57
58
|
|
|
58
59
|
```
|
|
59
|
-
Agent / Claude / Gemini
|
|
60
|
+
Agent / Claude / Gemini / Codex / Copilot / Vibe
|
|
60
61
|
│
|
|
61
62
|
├─── HTTP (homelab) ──→ https://linkedin.kpihx-labs.com/mcp (primary)
|
|
62
63
|
└─── stdio (local) ──→ ~/.bun/bin/linkedin-mcp serve (fallback)
|
|
@@ -84,37 +85,59 @@ Agent / Claude / Gemini
|
|
|
84
85
|
### CLI (`linkedin-admin`)
|
|
85
86
|
|
|
86
87
|
```bash
|
|
87
|
-
|
|
88
|
-
linkedin-admin
|
|
89
|
-
linkedin-admin
|
|
90
|
-
linkedin-admin
|
|
91
|
-
linkedin-admin
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
linkedin-admin
|
|
88
|
+
# Authentication
|
|
89
|
+
linkedin-admin auth [--port N] # OAuth flow — opens browser, saves token (default port 3001)
|
|
90
|
+
linkedin-admin token set <value> # Set access token directly (fetches profile, no browser)
|
|
91
|
+
linkedin-admin token unset # Clear stored token (same as logout)
|
|
92
|
+
linkedin-admin logout # Clear stored token
|
|
93
|
+
|
|
94
|
+
# Credential management (stored in admin env file)
|
|
95
|
+
linkedin-admin client-id set <v> # Set LINKEDIN_CLIENT_ID
|
|
96
|
+
linkedin-admin client-id unset # Clear LINKEDIN_CLIENT_ID
|
|
97
|
+
linkedin-admin client-secret set <v>
|
|
98
|
+
linkedin-admin client-secret unset
|
|
99
|
+
|
|
100
|
+
# Status & observability
|
|
101
|
+
linkedin-admin status # Credentials table + token summary
|
|
102
|
+
linkedin-admin status --json # Machine-readable JSON
|
|
103
|
+
linkedin-admin logs [--lines 50] # Tail admin log
|
|
104
|
+
linkedin-admin health # HTTP server reachability
|
|
105
|
+
linkedin-admin urls # All public/private endpoints
|
|
95
106
|
```
|
|
96
107
|
|
|
97
108
|
### HTTP admin
|
|
98
109
|
|
|
99
|
-
| Endpoint | Description |
|
|
100
|
-
|
|
101
|
-
|
|
|
102
|
-
|
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
|
|
|
110
|
+
| Endpoint | Method | Description |
|
|
111
|
+
|----------|--------|-------------|
|
|
112
|
+
| `/health` | GET | Liveness probe |
|
|
113
|
+
| `/admin/status` | GET | Full runtime status (token + Telegram state) |
|
|
114
|
+
| `/admin/help` | GET | All commands reference |
|
|
115
|
+
| `/admin/logs?lines=50` | GET | Recent admin log tail |
|
|
116
|
+
| `/admin/client-id/set` | POST | Body: `{ "value": "..." }` |
|
|
117
|
+
| `/admin/client-id/unset` | POST | Clear LINKEDIN_CLIENT_ID |
|
|
118
|
+
| `/admin/client-secret/set` | POST | Body: `{ "value": "..." }` |
|
|
119
|
+
| `/admin/client-secret/unset` | POST | Clear LINKEDIN_CLIENT_SECRET |
|
|
120
|
+
| `/admin/token/set` | POST | Body: `{ "value": "..." }` — sets token + fetches profile |
|
|
121
|
+
| `/admin/token/unset` | POST | Clear stored token |
|
|
122
|
+
| `/mcp` | POST/GET/DELETE | MCP Streamable HTTP transport |
|
|
106
123
|
|
|
107
124
|
### Telegram bot (optional)
|
|
108
125
|
|
|
109
|
-
Set `
|
|
126
|
+
Set `TELEGRAM_LINKEDIN_HOMELAB_TOKEN` + `TELEGRAM_CHAT_IDS` — server polls every 5 s.
|
|
110
127
|
|
|
111
128
|
| Command | Action |
|
|
112
129
|
|---------|--------|
|
|
113
130
|
| `/start` `/help` | Full command reference |
|
|
114
|
-
| `/status` |
|
|
131
|
+
| `/status` | Credentials + token status |
|
|
115
132
|
| `/health` | HTTP server health |
|
|
116
133
|
| `/urls` | Endpoint map |
|
|
117
134
|
| `/logs [n]` | Last n admin log lines (default 20) |
|
|
135
|
+
| `/token_set <value>` | Set access token (fetches profile) |
|
|
136
|
+
| `/token_unset` | Clear stored token |
|
|
137
|
+
| `/client_id_set <value>` | Set LINKEDIN_CLIENT_ID |
|
|
138
|
+
| `/client_id_unset` | Clear LINKEDIN_CLIENT_ID |
|
|
139
|
+
| `/client_secret_set <value>` | Set LINKEDIN_CLIENT_SECRET |
|
|
140
|
+
| `/client_secret_unset` | Clear LINKEDIN_CLIENT_SECRET |
|
|
118
141
|
|
|
119
142
|
---
|
|
120
143
|
|
|
@@ -124,14 +147,21 @@ Set `TELEGRAM_LINKEDIN_TOKEN` + `TELEGRAM_CHAT_IDS` and the server polls every 5
|
|
|
124
147
|
|
|
125
148
|
1. Go to [LinkedIn Developer Portal](https://developer.linkedin.com)
|
|
126
149
|
2. Create an app → add products: **"Sign In with LinkedIn using OpenID Connect"** + **"Share on LinkedIn"**
|
|
127
|
-
3. Set redirect URI: `http://localhost:
|
|
150
|
+
3. Set redirect URI: `http://localhost:3001/callback`
|
|
128
151
|
4. Note `Client ID` and `Client Secret`
|
|
129
152
|
|
|
130
153
|
### 2. Configure secrets
|
|
131
154
|
|
|
132
155
|
```bash
|
|
133
|
-
|
|
156
|
+
# Option A — via admin CLI (written to admin env file)
|
|
157
|
+
linkedin-admin client-id set <your_client_id>
|
|
158
|
+
linkedin-admin client-secret set <your_client_secret>
|
|
159
|
+
|
|
160
|
+
# Option B — via .env file
|
|
161
|
+
cp src/.env.example src/.env
|
|
134
162
|
# Fill in LINKEDIN_CLIENT_ID and LINKEDIN_CLIENT_SECRET
|
|
163
|
+
|
|
164
|
+
# Option C — via bw-env / kshrc (injected at login shell)
|
|
135
165
|
```
|
|
136
166
|
|
|
137
167
|
### 3. Install & authenticate
|
|
@@ -140,8 +170,14 @@ cp src/.env.example .env
|
|
|
140
170
|
bun install
|
|
141
171
|
bun link # editable install: ~/.bun/bin/linkedin-mcp + linkedin-admin
|
|
142
172
|
|
|
143
|
-
|
|
144
|
-
linkedin-admin
|
|
173
|
+
# Full OAuth flow (browser consent)
|
|
174
|
+
linkedin-admin auth # default port 3001
|
|
175
|
+
linkedin-admin auth --port 3002 # if 3001 is busy
|
|
176
|
+
|
|
177
|
+
# Or set access token directly (server/headless contexts)
|
|
178
|
+
linkedin-admin token set <your_access_token>
|
|
179
|
+
|
|
180
|
+
linkedin-admin status # verify credentials and token
|
|
145
181
|
```
|
|
146
182
|
|
|
147
183
|
### 4. Start MCP server
|
|
@@ -159,9 +195,9 @@ linkedin-mcp serve-http
|
|
|
159
195
|
## Docker / Homelab deployment
|
|
160
196
|
|
|
161
197
|
```bash
|
|
162
|
-
|
|
163
|
-
cp
|
|
164
|
-
# Edit
|
|
198
|
+
cd deploy
|
|
199
|
+
cp docker-compose.override.example.yml docker-compose.override.yml
|
|
200
|
+
# Edit override with your secrets (or rely on CI env injection)
|
|
165
201
|
|
|
166
202
|
docker compose up -d
|
|
167
203
|
```
|
|
@@ -170,6 +206,10 @@ Traefik labels in `deploy/docker-compose.yml` expose:
|
|
|
170
206
|
- `https://linkedin.kpihx-labs.com` (primary private trusted route)
|
|
171
207
|
- `https://linkedin.homelab` (fallback)
|
|
172
208
|
|
|
209
|
+
The container persists all state in `/data` (Docker volume):
|
|
210
|
+
- `/data/state/token.json` — OAuth token
|
|
211
|
+
- `/data/linkedin-admin.env` — credentials written by `token set` / `client-id set`
|
|
212
|
+
|
|
173
213
|
---
|
|
174
214
|
|
|
175
215
|
## Agent registration
|
|
@@ -192,11 +232,9 @@ Traefik labels in `deploy/docker-compose.yml` expose:
|
|
|
192
232
|
```json
|
|
193
233
|
{
|
|
194
234
|
"name": "linkedin-mcp",
|
|
195
|
-
"version": "0.1
|
|
235
|
+
"version": "0.2.1",
|
|
196
236
|
"mcpServers": {
|
|
197
|
-
"linkedin-mcp": {
|
|
198
|
-
"httpUrl": "https://linkedin.kpihx-labs.com/mcp"
|
|
199
|
-
},
|
|
237
|
+
"linkedin-mcp": { "httpUrl": "https://linkedin.kpihx-labs.com/mcp" },
|
|
200
238
|
"linkedin-mcp--fallback": {
|
|
201
239
|
"command": "/home/kpihx/.bun/bin/linkedin-mcp",
|
|
202
240
|
"args": ["serve"]
|
|
@@ -205,12 +243,49 @@ Traefik labels in `deploy/docker-compose.yml` expose:
|
|
|
205
243
|
}
|
|
206
244
|
```
|
|
207
245
|
|
|
246
|
+
### Codex (`~/.codex/config.toml`)
|
|
247
|
+
|
|
248
|
+
```toml
|
|
249
|
+
[mcp_servers.linkedin_mcp]
|
|
250
|
+
url = "https://linkedin.kpihx-labs.com/mcp"
|
|
251
|
+
|
|
252
|
+
[mcp_servers.linkedin_mcp_fallback]
|
|
253
|
+
command = "/home/kpihx/.bun/bin/linkedin-mcp"
|
|
254
|
+
args = ["serve"]
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Copilot (`~/.copilot/mcp-config.json`)
|
|
258
|
+
|
|
259
|
+
```json
|
|
260
|
+
"linkedin_mcp": { "type": "http", "url": "https://linkedin.kpihx-labs.com/mcp" },
|
|
261
|
+
"linkedin_mcp_fallback": {
|
|
262
|
+
"type": "stdio",
|
|
263
|
+
"command": "/home/kpihx/.bun/bin/linkedin-mcp",
|
|
264
|
+
"args": ["serve"]
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Vibe (`~/.vibe/config.toml`)
|
|
269
|
+
|
|
270
|
+
```toml
|
|
271
|
+
[[mcp_servers]]
|
|
272
|
+
name = "linkedin"
|
|
273
|
+
transport = "http"
|
|
274
|
+
url = "https://linkedin.kpihx-labs.com/mcp"
|
|
275
|
+
|
|
276
|
+
[[mcp_servers]]
|
|
277
|
+
name = "linkedin_fallback"
|
|
278
|
+
transport = "stdio"
|
|
279
|
+
command = "/home/kpihx/.bun/bin/linkedin-mcp"
|
|
280
|
+
args = ["serve"]
|
|
281
|
+
```
|
|
282
|
+
|
|
208
283
|
---
|
|
209
284
|
|
|
210
285
|
## Tests
|
|
211
286
|
|
|
212
287
|
```bash
|
|
213
|
-
bun test #
|
|
288
|
+
bun test # 61 tests, 5 files, 0 live API calls
|
|
214
289
|
bun test --watch # watch mode
|
|
215
290
|
```
|
|
216
291
|
|
|
@@ -223,11 +298,14 @@ All tests use mocked `fetch` and temp directories — no credentials needed.
|
|
|
223
298
|
LinkedIn personal OAuth tokens expire in **60 days**. There is no refresh token for non-Partner apps.
|
|
224
299
|
|
|
225
300
|
```
|
|
226
|
-
Day 0 → linkedin-admin auth
|
|
227
|
-
|
|
301
|
+
Day 0 → linkedin-admin auth (OAuth flow, browser consent)
|
|
302
|
+
OR linkedin-admin token set (direct token, headless/server)
|
|
303
|
+
Day 55+ → linkedin-admin status (shows days_left)
|
|
228
304
|
Day 60 → token invalid, re-run auth
|
|
229
305
|
```
|
|
230
306
|
|
|
307
|
+
Both flows produce a complete `token.json` with `member_id` populated — required for all posting tools.
|
|
308
|
+
|
|
231
309
|
---
|
|
232
310
|
|
|
233
311
|
## License
|
package/config.json
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"fallback_base_url": "https://linkedin.homelab"
|
|
9
9
|
},
|
|
10
10
|
"oauth": {
|
|
11
|
-
"redirect_port":
|
|
12
|
-
"redirect_uri": "http://localhost:
|
|
11
|
+
"redirect_port": 3001,
|
|
12
|
+
"redirect_uri": "http://localhost:3001/callback",
|
|
13
13
|
"scopes": ["openid", "profile", "email", "w_member_social"]
|
|
14
14
|
},
|
|
15
15
|
"logging": {
|
package/package.json
CHANGED
package/src/.env.example
CHANGED
|
@@ -7,8 +7,8 @@ LINKEDIN_CLIENT_SECRET=your_client_secret_here
|
|
|
7
7
|
# LINKEDIN_ACCESS_TOKEN=your_access_token_here
|
|
8
8
|
|
|
9
9
|
# Optional: Telegram admin bot
|
|
10
|
-
#
|
|
11
|
-
# TELEGRAM_CHAT_IDS=123456789,987654321
|
|
10
|
+
# TELEGRAM_LINKEDIN_HOMELAB_TOKEN=your_bot_token_here (from @BotFather)
|
|
11
|
+
# TELEGRAM_CHAT_IDS=123456789,987654321 (comma-separated chat IDs)
|
|
12
12
|
|
|
13
13
|
# Optional server overrides
|
|
14
14
|
# LINKEDIN_MCP_HTTP_HOST=0.0.0.0
|