Skip to content

feat(cookie): implement LinkedIn cookie extraction and usage #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 6, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,22 @@
},
"problemMatcher": []
},
{
"label": "bunx @anthropic-ai/dxt pack",
"detail": "Pack the DXT package",
"type": "shell",
"command": "bunx",
"args": ["@anthropic-ai/dxt", "pack"],
"group": {
"kind": "build",
"isDefault": false
},
"presentation": {
"reveal": "always",
"panel": "new",
"focus": true
},
"problemMatcher": []
}
]
}
103 changes: 91 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,38 +36,69 @@ Suggest improvements for my CV to target this job posting https://www.linkedin.c
> [!NOTE]
> 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).

---
<br/>
<br/>

## 🐳 Docker Setup (Recommended - Universal)

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

**Zero setup required** - just add the mcp server to your client config and replace email and password with your linkedin credentials.

### Installation

**Claude Desktop:**
**Client Configuration:**
```json
{
"mcpServers": {
"linkedin": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "LINKEDIN_EMAIL",
"-e", "LINKEDIN_PASSWORD",
"-e", "LINKEDIN_COOKIE",
"stickerdaniel/linkedin-mcp-server",
"--no-setup"
],
"env": {
"LINKEDIN_EMAIL": "your.email@example.com",
"LINKEDIN_PASSWORD": "your_password"
"LINKEDIN_COOKIE": "XXXXXX...",
}
}
}
}
```

### Getting the LinkedIn Cookie
<details>
<summary><b>🐳 Docker get-cookie method</b></summary>

**Run the server with the `--get-cookie` flag:**
```bash
docker run -i --rm \
-e LINKEDIN_EMAIL="your.email@example.com" \
-e LINKEDIN_PASSWORD="your_password" \
stickerdaniel/linkedin-mcp-server \
--get-cookie
```
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.
</details>
<details>
<summary><b>🌐 Chrome DevTools Guide</b></summary>

1. Open LinkedIn and login
2. Open Chrome DevTools (F12 or right-click → Inspect)
3. Go to **Application** > **Storage** > **Cookies** > **https://www.linkedin.com**
4. Find the cookie named `li_at`
5. Copy the **Value** field (this is your LinkedIn session cookie)
6. Use this value as your `LINKEDIN_COOKIE` in the configuration

</details>
<br/>

> [!NOTE]
> The cookie will expire during the next 30 days. Just get the new cookie and update your config.

> [!TIP]
> There are also many cookie manager extensions that you can use to easily get the cookie.

### Docker Setup Help
<details>
<summary><b>🔧 Configuration</b></summary>

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

**HTTP Mode Example (for web-based MCP clients):**
```bash
Expand Down Expand Up @@ -116,6 +149,9 @@ docker run -i --rm \
- 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)
</details>

<br/>
<br/>

## 📦 Claude Desktop (DXT Extension)

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

### Getting the LinkedIn Cookie
<details>
<summary><b>🐳 Docker get-cookie method</b></summary>

**Run the server with the `--get-cookie` flag:**
```bash
docker run -i --rm \
-e LINKEDIN_EMAIL="your.email@example.com" \
-e LINKEDIN_PASSWORD="your_password" \
stickerdaniel/linkedin-mcp-server \
--get-cookie
```
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.
</details>
<details>
<summary><b>🌐 Chrome DevTools Guide</b></summary>

1. Open LinkedIn and login
2. Open Chrome DevTools (F12 or right-click → Inspect)
3. Go to **Application** > **Storage** > **Cookies** > **https://www.linkedin.com**
4. Find the cookie named `li_at`
5. Copy the **Value** field (this is your LinkedIn session cookie)
6. Use this value as your `LINKEDIN_COOKIE` in the configuration

</details>
<br/>

> [!NOTE]
> The cookie will expire during the next 30 days. Just get the new cookie and update your config.

> [!TIP]
> There are also many cookie manager extensions that you can use to easily get the cookie.

### DXT Extension Setup Help
<details>
<summary><b>❗ Troubleshooting</b></summary>

Expand All @@ -139,6 +209,9 @@ docker run -i --rm \
- 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)
</details>

<br/>
<br/>

## 🐍 Local Setup (Develop & Contribute)

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

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

### Local Setup Help
<details>
<summary><b>🔧 Configuration</b></summary>

**CLI Options:**
- `--no-headless` - Show browser window (debugging)
- `--debug` - Enable detailed logging
- `--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)
- `--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)
- `--no-lazy-init` - Login to LinkedIn immediately instead of waiting for the first tool call
- `--get-cookie` - Login with email and password and extract the LinkedIn cookie
- `--cookie {cookie}` - Pass a specific LinkedIn cookie for login

**Claude Desktop:**
```json
```**json**
{
"mcpServers": {
"linkedin": {
Expand Down Expand Up @@ -217,7 +294,9 @@ uv run main.py --no-headless --no-lazy-init

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

---

<br/>
<br/>

## License

Expand Down
171 changes: 171 additions & 0 deletions linkedin_mcp_server/authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# linkedin_mcp_server/authentication.py
"""
Pure authentication module for LinkedIn MCP Server.

This module handles authentication without any driver dependencies.
"""

import logging

from linkedin_mcp_server.config import get_config
from linkedin_mcp_server.config.providers import (
get_cookie_from_keyring,
save_cookie_to_keyring,
clear_cookie_from_keyring,
)
from linkedin_mcp_server.exceptions import CredentialsNotFoundError

# Constants for cookie validation
MIN_COOKIE_LENGTH = 20
MIN_RAW_COOKIE_LENGTH = 10

logger = logging.getLogger(__name__)


def has_authentication() -> bool:
"""
Check if authentication is available without triggering setup.

Returns:
bool: True if authentication (cookie) is available, False otherwise
"""
config = get_config()

# Check environment variable
if config.linkedin.cookie:
return True

# Check keyring if enabled
if config.linkedin.use_keyring:
cookie = get_cookie_from_keyring()
if cookie:
return True

return False


def get_authentication() -> str:
"""
Get LinkedIn cookie from available sources.

Returns:
str: LinkedIn session cookie

Raises:
CredentialsNotFoundError: If no authentication is available
"""
config = get_config()

# First, try environment variable
if config.linkedin.cookie:
logger.info("Using LinkedIn cookie from environment")
return config.linkedin.cookie

# Second, try keyring if enabled
if config.linkedin.use_keyring:
cookie = get_cookie_from_keyring()
if cookie:
logger.info("Using LinkedIn cookie from keyring")
return cookie

# No authentication available
raise CredentialsNotFoundError("No LinkedIn cookie found")


def store_authentication(cookie: str) -> bool:
"""
Store LinkedIn cookie securely.

Args:
cookie: LinkedIn session cookie to store

Returns:
bool: True if storage was successful, False otherwise
"""
config = get_config()

if config.linkedin.use_keyring:
success = save_cookie_to_keyring(cookie)
if success:
logger.info("Cookie stored securely in keyring")
else:
logger.warning("Could not store cookie in system keyring")
return success
else:
logger.info("Keyring disabled, cookie not stored")
return False


def clear_authentication() -> bool:
"""
Clear stored authentication.

Returns:
bool: True if clearing was successful, False otherwise
"""
config = get_config()

if config.linkedin.use_keyring:
success = clear_cookie_from_keyring()
if success:
logger.info("Authentication cleared from keyring")
else:
logger.warning("Could not clear authentication from keyring")
return success
else:
logger.info("Keyring disabled, nothing to clear")
return True


def validate_cookie_format(cookie: str) -> bool:
"""
Validate that the cookie has the expected format.

Args:
cookie: Cookie string to validate

Returns:
bool: True if cookie format is valid, False otherwise
"""
if not cookie:
return False

# LinkedIn session cookies typically start with "li_at="
if cookie.startswith("li_at=") and len(cookie) > MIN_COOKIE_LENGTH:
return True

# Also accept raw cookie values (without li_at= prefix)
if (
not cookie.startswith("li_at=")
and len(cookie) > MIN_RAW_COOKIE_LENGTH
and "=" not in cookie
):
return True

return False


def ensure_authentication() -> str:
"""
Ensure authentication is available, raising clear error if not.

Returns:
str: LinkedIn session cookie

Raises:
CredentialsNotFoundError: If no authentication is available with clear instructions
"""
try:
return get_authentication()
except CredentialsNotFoundError:
config = get_config()

if config.chrome.non_interactive:
raise CredentialsNotFoundError(
"No LinkedIn cookie found. Please provide cookie via "
"environment variable (LINKEDIN_COOKIE) or run with --get-cookie to obtain one."
)
else:
raise CredentialsNotFoundError(
"No LinkedIn authentication found. Please run setup to configure authentication."
)
Loading