Skip to content

Commit d9dd1cf

Browse files
Merge pull request #21 from stickerdaniel/feature/cookie
2 parents 7878e0b + ce05735 commit d9dd1cf

File tree

15 files changed

+1101
-366
lines changed

15 files changed

+1101
-366
lines changed

.vscode/tasks.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,5 +133,22 @@
133133
},
134134
"problemMatcher": []
135135
},
136+
{
137+
"label": "bunx @anthropic-ai/dxt pack",
138+
"detail": "Pack the DXT package",
139+
"type": "shell",
140+
"command": "bunx",
141+
"args": ["@anthropic-ai/dxt", "pack"],
142+
"group": {
143+
"kind": "build",
144+
"isDefault": false
145+
},
146+
"presentation": {
147+
"reveal": "always",
148+
"panel": "new",
149+
"focus": true
150+
},
151+
"problemMatcher": []
152+
}
136153
]
137154
}

README.md

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,38 +36,69 @@ Suggest improvements for my CV to target this job posting https://www.linkedin.c
3636
> [!NOTE]
3737
> July 2025: All tools are currently functional and actively maintained. If you encounter any issues, please report them in the [GitHub issues](https://github.com/stickerdaniel/linkedin-mcp-server/issues).
3838
39-
---
39+
<br/>
40+
<br/>
4041

4142
## 🐳 Docker Setup (Recommended - Universal)
4243

4344
**Prerequisites:** Make sure you have [Docker](https://www.docker.com/get-started/) installed and running.
4445

45-
**Zero setup required** - just add the mcp server to your client config and replace email and password with your linkedin credentials.
46-
4746
### Installation
4847

49-
**Claude Desktop:**
48+
**Client Configuration:**
5049
```json
5150
{
5251
"mcpServers": {
5352
"linkedin": {
5453
"command": "docker",
5554
"args": [
5655
"run", "-i", "--rm",
57-
"-e", "LINKEDIN_EMAIL",
58-
"-e", "LINKEDIN_PASSWORD",
56+
"-e", "LINKEDIN_COOKIE",
5957
"stickerdaniel/linkedin-mcp-server",
6058
"--no-setup"
6159
],
6260
"env": {
63-
"LINKEDIN_EMAIL": "your.email@example.com",
64-
"LINKEDIN_PASSWORD": "your_password"
61+
"LINKEDIN_COOKIE": "XXXXXX...",
6562
}
6663
}
6764
}
6865
}
6966
```
7067

68+
### Getting the LinkedIn Cookie
69+
<details>
70+
<summary><b>🐳 Docker get-cookie method</b></summary>
71+
72+
**Run the server with the `--get-cookie` flag:**
73+
```bash
74+
docker run -i --rm \
75+
-e LINKEDIN_EMAIL="your.email@example.com" \
76+
-e LINKEDIN_PASSWORD="your_password" \
77+
stickerdaniel/linkedin-mcp-server \
78+
--get-cookie
79+
```
80+
Copy the cookie from the output and set it as `LINKEDIN_COOKIE` in your client configuration. If this fails with a captcha challenge, use the method below.
81+
</details>
82+
<details>
83+
<summary><b>🌐 Chrome DevTools Guide</b></summary>
84+
85+
1. Open LinkedIn and login
86+
2. Open Chrome DevTools (F12 or right-click → Inspect)
87+
3. Go to **Application** > **Storage** > **Cookies** > **https://www.linkedin.com**
88+
4. Find the cookie named `li_at`
89+
5. Copy the **Value** field (this is your LinkedIn session cookie)
90+
6. Use this value as your `LINKEDIN_COOKIE` in the configuration
91+
92+
</details>
93+
<br/>
94+
95+
> [!NOTE]
96+
> The cookie will expire during the next 30 days. Just get the new cookie and update your config.
97+
98+
> [!TIP]
99+
> There are also many cookie manager extensions that you can use to easily get the cookie.
100+
101+
### Docker Setup Help
71102
<details>
72103
<summary><b>🔧 Configuration</b></summary>
73104

@@ -83,6 +114,8 @@ Suggest improvements for my CV to target this job posting https://www.linkedin.c
83114
- `--host HOST` - HTTP server host (default: 127.0.0.1)
84115
- `--port PORT` - HTTP server port (default: 8000)
85116
- `--path PATH` - HTTP server path (default: /mcp)
117+
- `--get-cookie` - Attempt to login with email and password and extract the LinkedIn cookie
118+
- `--cookie {cookie}` - Pass a specific LinkedIn cookie for login
86119

87120
**HTTP Mode Example (for web-based MCP clients):**
88121
```bash
@@ -116,6 +149,9 @@ docker run -i --rm \
116149
- You might get a captcha challenge if you logged in a lot of times in a short period of time, then try again later or follow the [local setup instructions](#-local-setup-develop--contribute) to run the server manually in --no-headless mode where you can debug the login process (solve captcha manually)
117150
</details>
118151

152+
<br/>
153+
<br/>
154+
119155
## 📦 Claude Desktop (DXT Extension)
120156

121157
**Prerequisites:** [Claude Desktop](https://claude.ai/download) and [Docker](https://www.docker.com/get-started/) installed
@@ -126,6 +162,40 @@ docker run -i --rm \
126162
3. Configure your LinkedIn credentials when prompted
127163
4. Start using LinkedIn tools immediately
128164

165+
### Getting the LinkedIn Cookie
166+
<details>
167+
<summary><b>🐳 Docker get-cookie method</b></summary>
168+
169+
**Run the server with the `--get-cookie` flag:**
170+
```bash
171+
docker run -i --rm \
172+
-e LINKEDIN_EMAIL="your.email@example.com" \
173+
-e LINKEDIN_PASSWORD="your_password" \
174+
stickerdaniel/linkedin-mcp-server \
175+
--get-cookie
176+
```
177+
Copy the cookie from the output and set it as `LINKEDIN_COOKIE` in your client configuration. If this fails with a captcha challenge, use the method below.
178+
</details>
179+
<details>
180+
<summary><b>🌐 Chrome DevTools Guide</b></summary>
181+
182+
1. Open LinkedIn and login
183+
2. Open Chrome DevTools (F12 or right-click → Inspect)
184+
3. Go to **Application** > **Storage** > **Cookies** > **https://www.linkedin.com**
185+
4. Find the cookie named `li_at`
186+
5. Copy the **Value** field (this is your LinkedIn session cookie)
187+
6. Use this value as your `LINKEDIN_COOKIE` in the configuration
188+
189+
</details>
190+
<br/>
191+
192+
> [!NOTE]
193+
> The cookie will expire during the next 30 days. Just get the new cookie and update your config.
194+
195+
> [!TIP]
196+
> There are also many cookie manager extensions that you can use to easily get the cookie.
197+
198+
### DXT Extension Setup Help
129199
<details>
130200
<summary><b>❗ Troubleshooting</b></summary>
131201

@@ -139,6 +209,9 @@ docker run -i --rm \
139209
- You might get a captcha challenge if you logged in a lot of times in a short period of time, then try again later or follow the [local setup instructions](#-local-setup-develop--contribute) to run the server manually in --no-headless mode where you can debug the login process (solve captcha manually)
140210
</details>
141211

212+
<br/>
213+
<br/>
214+
142215
## 🐍 Local Setup (Develop & Contribute)
143216

144217
**Prerequisites:** [Chrome browser](https://www.google.com/chrome/) and [Git](https://git-scm.com/downloads) installed
@@ -170,21 +243,26 @@ uv sync --group dev
170243
uv run pre-commit install
171244

172245
# 5. Start the server once manually
173-
# (you will be prompted to enter your LinkedIn credentials, and they are securely stored in your OS keychain)
246+
# You will be prompted to enter your LinkedIn credentials, and they will be securely stored in your OS keychain
247+
# Once logged in, your cookie will be stored in your OS keychain and used for subsequent runs until it expires
174248
uv run main.py --no-headless --no-lazy-init
175249
```
176250

251+
### Local Setup Help
177252
<details>
178253
<summary><b>🔧 Configuration</b></summary>
179254

180255
**CLI Options:**
181256
- `--no-headless` - Show browser window (debugging)
182257
- `--debug` - Enable detailed logging
183-
- `--no-setup` - Skip credential prompts (make sure to set `LINKEDIN_EMAIL` and `LINKEDIN_PASSWORD` in env or or run the server once manualy, then it will be stored in your OS keychain and you can run the server without credentials)
258+
- `--no-setup` - Skip credential prompts (make sure to set `LINKEDIN_COOKIE` or `LINKEDIN_EMAIL` and `LINKEDIN_PASSWORD` in env or that you run the server once manually, so the authentication is stored in your OS keychain and you can run the server without credentials)
184259
- `--no-lazy-init` - Login to LinkedIn immediately instead of waiting for the first tool call
260+
- `--get-cookie` - Login with email and password and extract the LinkedIn cookie
261+
- `--cookie {cookie}` - Pass a specific LinkedIn cookie for login
262+
- `--help` - Show help
185263

186264
**Claude Desktop:**
187-
```json
265+
```**json**
188266
{
189267
"mcpServers": {
190268
"linkedin": {
@@ -217,7 +295,9 @@ uv run main.py --no-headless --no-lazy-init
217295

218296
Feel free to open an [issue](https://github.com/stickerdaniel/linkedin-mcp-server/issues) or [PR](https://github.com/stickerdaniel/linkedin-mcp-server/pulls)!
219297

220-
---
298+
299+
<br/>
300+
<br/>
221301

222302
## License
223303

linkedin_mcp_server/authentication.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# linkedin_mcp_server/authentication.py
2+
"""
3+
Pure authentication module for LinkedIn MCP Server.
4+
5+
This module handles authentication without any driver dependencies.
6+
"""
7+
8+
import logging
9+
10+
from linkedin_mcp_server.config import get_config
11+
from linkedin_mcp_server.config.providers import (
12+
clear_cookie_from_keyring,
13+
get_cookie_from_keyring,
14+
save_cookie_to_keyring,
15+
)
16+
from linkedin_mcp_server.exceptions import CredentialsNotFoundError
17+
18+
# Constants for cookie validation
19+
MIN_RAW_COOKIE_LENGTH = 110
20+
MIN_COOKIE_LENGTH = MIN_RAW_COOKIE_LENGTH + len("li_at=")
21+
22+
logger = logging.getLogger(__name__)
23+
24+
25+
def has_authentication() -> bool:
26+
"""
27+
Check if authentication is available without triggering setup.
28+
29+
Returns:
30+
bool: True if authentication (cookie) is available, False otherwise
31+
"""
32+
config = get_config()
33+
34+
# Check environment variable
35+
if config.linkedin.cookie:
36+
return True
37+
38+
# Check keyring if enabled
39+
if config.linkedin.use_keyring:
40+
cookie = get_cookie_from_keyring()
41+
if cookie:
42+
return True
43+
44+
return False
45+
46+
47+
def get_authentication() -> str:
48+
"""
49+
Get LinkedIn cookie from available sources.
50+
51+
Returns:
52+
str: LinkedIn session cookie
53+
54+
Raises:
55+
CredentialsNotFoundError: If no authentication is available
56+
"""
57+
config = get_config()
58+
59+
# First, try environment variable
60+
if config.linkedin.cookie:
61+
logger.info("Using LinkedIn cookie from environment")
62+
return config.linkedin.cookie
63+
64+
# Second, try keyring if enabled
65+
if config.linkedin.use_keyring:
66+
cookie = get_cookie_from_keyring()
67+
if cookie:
68+
logger.info("Using LinkedIn cookie from keyring")
69+
return cookie
70+
71+
# No authentication available
72+
raise CredentialsNotFoundError("No LinkedIn cookie found")
73+
74+
75+
def store_authentication(cookie: str) -> bool:
76+
"""
77+
Store LinkedIn cookie securely.
78+
79+
Args:
80+
cookie: LinkedIn session cookie to store
81+
82+
Returns:
83+
bool: True if storage was successful, False otherwise
84+
"""
85+
config = get_config()
86+
87+
if config.linkedin.use_keyring:
88+
success = save_cookie_to_keyring(cookie)
89+
if success:
90+
logger.info("Cookie stored securely in keyring")
91+
else:
92+
logger.warning("Could not store cookie in system keyring")
93+
return success
94+
else:
95+
logger.info("Keyring disabled, cookie not stored")
96+
return False
97+
98+
99+
def clear_authentication() -> bool:
100+
"""
101+
Clear stored authentication.
102+
103+
Returns:
104+
bool: True if clearing was successful, False otherwise
105+
"""
106+
config = get_config()
107+
108+
if config.linkedin.use_keyring:
109+
success = clear_cookie_from_keyring()
110+
if success:
111+
logger.info("Authentication cleared from keyring")
112+
else:
113+
logger.warning("Could not clear authentication from keyring")
114+
return success
115+
else:
116+
logger.info("Keyring disabled, nothing to clear")
117+
return True
118+
119+
120+
def validate_cookie_format(cookie: str) -> bool:
121+
"""
122+
Validate that the cookie has the expected format.
123+
124+
Args:
125+
cookie: Cookie string to validate
126+
127+
Returns:
128+
bool: True if cookie format is valid, False otherwise
129+
"""
130+
if not cookie:
131+
return False
132+
133+
# LinkedIn session cookies typically start with "li_at="
134+
if cookie.startswith("li_at=") and len(cookie) > MIN_COOKIE_LENGTH:
135+
return True
136+
137+
# Also accept raw cookie values (without li_at= prefix)
138+
if (
139+
not cookie.startswith("li_at=")
140+
and len(cookie) > MIN_RAW_COOKIE_LENGTH
141+
and "=" not in cookie
142+
):
143+
return True
144+
145+
return False
146+
147+
148+
def ensure_authentication() -> str:
149+
"""
150+
Ensure authentication is available, raising clear error if not.
151+
152+
Returns:
153+
str: LinkedIn session cookie
154+
155+
Raises:
156+
CredentialsNotFoundError: If no authentication is available with clear instructions
157+
"""
158+
try:
159+
return get_authentication()
160+
except CredentialsNotFoundError:
161+
config = get_config()
162+
163+
if config.chrome.non_interactive:
164+
raise CredentialsNotFoundError(
165+
"No LinkedIn cookie found. Please provide cookie via "
166+
"environment variable (LINKEDIN_COOKIE) or run with --get-cookie to obtain one."
167+
)
168+
else:
169+
raise CredentialsNotFoundError(
170+
"No LinkedIn authentication found. Please run setup to configure authentication."
171+
)

0 commit comments

Comments
 (0)