Skip to content

feat(mcp): implement http transport support #17

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 7 commits into from
Jul 4, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
52 changes: 49 additions & 3 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "bunx @modelcontextprotocol/inspector",
"detail": "Run the Model Context Protocol Inspector",
"type": "shell",
"command": "bunx",
"args": ["@modelcontextprotocol/inspector"],
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new",
"focus": true
},
"problemMatcher": []
},
{
"label": "uv run pre-commit run --all-files",
"detail": "Run pre-commit hooks on all files",
Expand All @@ -14,7 +31,7 @@
],
"group": {
"kind": "test",
"isDefault": true
"isDefault": false
},
"presentation": {
"reveal": "never",
Expand Down Expand Up @@ -57,9 +74,38 @@
"--no-headless",
"--no-lazy-init"
],
"group": {
"kind": "build"
},
"presentation": {
"reveal": "always",
"panel": "new",
"focus": true
},
"problemMatcher": []
},
{
"label": "uv run main.py --transport streamable-http --no-setup",
"detail": "Start HTTP MCP server on localhost:8000/mcp",
"type": "shell",
"command": "uv",
"args": [
"run",
"main.py",
"--transport",
"streamable-http",
"--host",
"127.0.0.1",
"--port",
"8000",
"--path",
"/mcp",
"--no-setup"
],
"isBackground": true,
"group": {
"kind": "build",
"isDefault": true
"isDefault": false
},
"presentation": {
"reveal": "always",
Expand All @@ -86,6 +132,6 @@
"focus": false
},
"problemMatcher": []
}
},
]
}
7 changes: 4 additions & 3 deletions linkedin_mcp_server/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
This module handles the command-line interface and configuration management.
"""

from typing import Dict, Any, List
import os
import json
import subprocess
import logging
import os
import subprocess
from typing import Any, Dict, List

import pyperclip # type: ignore

from linkedin_mcp_server.config import get_config
Expand Down
46 changes: 44 additions & 2 deletions linkedin_mcp_server/config/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ def load_from_env(config: AppConfig) -> AppConfig:
# Headless mode
if os.environ.get("HEADLESS") in ("0", "false", "False", "no", "No"):
config.chrome.headless = False
elif os.environ.get("HEADLESS") in ("1", "true", "True", "yes", "Yes"):
config.chrome.headless = True

# Non-interactive mode
if os.environ.get("NON_INTERACTIVE") in ("1", "true", "True", "yes", "Yes"):
config.chrome.non_interactive = True

# Lazy initialization
if os.environ.get("LAZY_INIT") in ("1", "true", "True", "yes", "Yes"):
config.server.lazy_init = True
elif os.environ.get("LAZY_INIT") in ("0", "false", "False", "no", "No"):
config.server.lazy_init = False

return config

Expand Down Expand Up @@ -80,9 +92,30 @@ def load_from_args(config: AppConfig) -> AppConfig:

parser.add_argument(
"--transport",
choices=["stdio", "sse"],
choices=["stdio", "streamable-http"],
default=None,
help="Specify the transport mode (stdio or streamable-http)",
)

parser.add_argument(
"--host",
type=str,
default=None,
help="HTTP server host (default: 127.0.0.1)",
)

parser.add_argument(
"--port",
type=int,
default=None,
help="HTTP server port (default: 8000)",
)

parser.add_argument(
"--path",
type=str,
default=None,
help="Specify the transport mode (stdio or sse)",
help="HTTP server path (default: /mcp)",
)

parser.add_argument(
Expand All @@ -109,6 +142,15 @@ def load_from_args(config: AppConfig) -> AppConfig:
if args.transport:
config.server.transport = args.transport

if args.host:
config.server.host = args.host

if args.port:
config.server.port = args.port

if args.path:
config.server.path = args.path

if args.chromedriver:
config.chrome.chromedriver_path = args.chromedriver

Expand Down
6 changes: 5 additions & 1 deletion linkedin_mcp_server/config/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@ class LinkedInConfig:
class ServerConfig:
"""MCP server configuration."""

transport: Literal["stdio", "sse"] = "stdio"
transport: Literal["stdio", "streamable-http"] = "stdio"
lazy_init: bool = True
debug: bool = False
setup: bool = True
# HTTP transport configuration
host: str = "127.0.0.1"
port: int = 8000
path: str = "/mcp"


@dataclass
Expand Down
7 changes: 5 additions & 2 deletions linkedin_mcp_server/config/secrets.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# src/linkedin_mcp_server/config/secrets.py
from typing import Dict, Optional
import logging
from typing import Dict, Optional

import inquirer # type: ignore

from linkedin_mcp_server.config import get_config

from .providers import (
get_credentials_from_keyring,
save_credentials_to_keyring,
get_keyring_name,
save_credentials_to_keyring,
)

logger = logging.getLogger(__name__)
Expand Down
10 changes: 6 additions & 4 deletions linkedin_mcp_server/drivers/chrome.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
This module handles the creation and management of Chrome WebDriver instances.
"""

import os
import sys
from typing import Dict, Optional
import os

import inquirer # type: ignore
from selenium import webdriver
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.common.exceptions import WebDriverException
import inquirer # type: ignore

from linkedin_mcp_server.config import get_config
from linkedin_mcp_server.config.secrets import get_credentials
from linkedin_mcp_server.config.providers import clear_credentials_from_keyring
from linkedin_mcp_server.config.secrets import get_credentials

# Global driver storage to reuse sessions
active_drivers: Dict[str, webdriver.Chrome] = {}
Expand Down
7 changes: 4 additions & 3 deletions linkedin_mcp_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
This module creates the MCP server and registers all the LinkedIn tools.
"""

from typing import Dict, Any
from mcp.server.fastmcp import FastMCP
from typing import Any, Dict

from fastmcp import FastMCP

from linkedin_mcp_server.drivers.chrome import active_drivers
from linkedin_mcp_server.tools.person import register_person_tools
from linkedin_mcp_server.tools.company import register_company_tools
from linkedin_mcp_server.tools.job import register_job_tools
from linkedin_mcp_server.tools.person import register_person_tools


def create_mcp_server() -> FastMCP:
Expand Down
5 changes: 3 additions & 2 deletions linkedin_mcp_server/tools/company.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
This module provides tools for scraping LinkedIn company profiles.
"""

from typing import Dict, Any, List
from mcp.server.fastmcp import FastMCP
from typing import Any, Dict, List

from fastmcp import FastMCP
from linkedin_scraper import Company

from linkedin_mcp_server.drivers.chrome import get_or_create_driver
Expand Down
2 changes: 1 addition & 1 deletion linkedin_mcp_server/tools/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

from typing import Any, Dict, List

from fastmcp import FastMCP
from linkedin_scraper import Job, JobSearch
from mcp.server.fastmcp import FastMCP

from linkedin_mcp_server.drivers.chrome import get_or_create_driver

Expand Down
5 changes: 3 additions & 2 deletions linkedin_mcp_server/tools/person.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
This module provides tools for scraping LinkedIn person profiles.
"""

from typing import Dict, Any, List
from mcp.server.fastmcp import FastMCP
from typing import Any, Dict, List

from fastmcp import FastMCP
from linkedin_scraper import Person

from linkedin_mcp_server.drivers.chrome import get_or_create_driver
Expand Down
25 changes: 19 additions & 6 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,29 @@
LinkedIn MCP Server - A Model Context Protocol server for LinkedIn integration.
"""

import sys
import logging
import inquirer # type: ignore
import sys
from typing import Literal

import inquirer # type: ignore

from linkedin_mcp_server.cli import print_claude_config

# Import the new centralized configuration
from linkedin_mcp_server.config import get_config
from linkedin_mcp_server.cli import print_claude_config
from linkedin_mcp_server.drivers.chrome import initialize_driver
from linkedin_mcp_server.server import create_mcp_server, shutdown_handler


def choose_transport_interactive() -> Literal["stdio", "sse"]:
def choose_transport_interactive() -> Literal["stdio", "streamable-http"]:
"""Prompt user for transport mode using inquirer."""
questions = [
inquirer.List(
"transport",
message="Choose mcp transport mode",
choices=[
("stdio (Default CLI mode)", "stdio"),
("sse (Server-Sent Events HTTP mode)", "sse"),
("streamable-http (HTTP server mode)", "streamable-http"),
],
default="stdio",
)
Expand Down Expand Up @@ -67,7 +69,18 @@ def main() -> None:

# Start server
print(f"\n🚀 Running LinkedIn MCP server ({transport.upper()} mode)...")
mcp.run(transport=transport)
if transport == "streamable-http":
print(
f"📡 HTTP server will be available at http://{config.server.host}:{config.server.port}{config.server.path}"
)
mcp.run(
transport=transport,
host=config.server.host,
port=config.server.port,
path=config.server.path,
)
else:
mcp.run(transport=transport)


def exit_gracefully(exit_code: int = 0) -> None:
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ description = "MCP server for LinkedIn profile, company, and job scraping with C
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"httpx>=0.28.1",
"fastmcp>=2.10.1",
"inquirer>=3.4.0",
"keyring>=25.6.0",
"linkedin-scraper",
"mcp[cli]>=1.6.0",
"pyperclip>=1.9.0",
]

Expand All @@ -21,8 +20,10 @@ linkedin-scraper = { git = "https://github.com/joeyism/linkedin_scraper.git" }

[dependency-groups]
dev = [
"aiohttp>=3.12.13",
"pre-commit>=4.2.0",
"pytest>=8.3.5",
"pytest-asyncio>=1.0.0",
"pytest-cov>=6.1.1",
"ruff>=0.11.11",
"ty>=0.0.1a12",
Expand Down
29 changes: 0 additions & 29 deletions smithery.yaml

This file was deleted.

Loading