Skip to content

Commit 3c4004a

Browse files
committed
feat(smithery): add Dockerfile and main server implementation for HTTP transport
1 parent eda69cf commit 3c4004a

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

Dockerfile.smithery

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
FROM python:3.12-alpine
2+
3+
# Install system dependencies including Chromium and ChromeDriver
4+
RUN apk add --no-cache \
5+
git \
6+
curl \
7+
chromium \
8+
chromium-chromedriver
9+
10+
# Install uv from official image
11+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
12+
13+
# Set working directory
14+
WORKDIR /app
15+
16+
# Copy project files
17+
COPY . /app
18+
19+
# Sync dependencies and install project
20+
RUN --mount=type=cache,target=/root/.cache/uv \
21+
uv sync --frozen
22+
23+
# Create a non-root user
24+
RUN adduser -D -u 1000 mcpuser && chown -R mcpuser:mcpuser /app
25+
USER mcpuser
26+
27+
# Expose the port that will be set via PORT env var
28+
EXPOSE 8000
29+
30+
# Smithery command - uses HTTP transport and PORT env var
31+
CMD ["uv", "run", "python", "smithery_main.py"]

smithery.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
runtime: "container"
2+
build:
3+
dockerfile: "Dockerfile.smithery" # Smithery-specific Dockerfile
4+
dockerBuildPath: "." # Docker build context
5+
startCommand:
6+
type: "http"
7+
configSchema: # JSON Schema for configuration
8+
type: "object"
9+
properties:
10+
linkedin_email:
11+
type: "string"
12+
description: "LinkedIn email address for authentication"
13+
linkedin_password:
14+
type: "string"
15+
description: "LinkedIn password for authentication"
16+
sensitive: true
17+
required: ["linkedin_email", "linkedin_password"]
18+
exampleConfig:
19+
linkedin_email: "user@example.com"
20+
linkedin_password: "password123"

smithery_main.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# smithery_main.py
2+
"""
3+
LinkedIn MCP Server - Smithery HTTP Transport Entry Point
4+
5+
This entry point is specifically designed for Smithery deployment with:
6+
- HTTP transport (streamable-http)
7+
- Query parameter configuration parsing
8+
- PORT environment variable support
9+
- Uses existing lazy authentication system
10+
"""
11+
12+
import os
13+
import logging
14+
from urllib.parse import parse_qs
15+
16+
from linkedin_mcp_server.config import get_config, reset_config
17+
from linkedin_mcp_server.drivers.chrome import initialize_driver
18+
from linkedin_mcp_server.server import create_mcp_server, shutdown_handler
19+
20+
21+
def setup_smithery_environment(query_string: str | None = None) -> None:
22+
"""
23+
Set up environment variables from Smithery query parameters.
24+
25+
Args:
26+
query_string: Query parameters from Smithery configuration
27+
"""
28+
if not query_string:
29+
return
30+
31+
# Parse query parameters
32+
parsed = parse_qs(query_string)
33+
34+
# Map Smithery parameters to environment variables
35+
param_mapping = {
36+
"linkedin_email": "LINKEDIN_EMAIL",
37+
"linkedin_password": "LINKEDIN_PASSWORD",
38+
}
39+
40+
for param, env_var in param_mapping.items():
41+
if param in parsed and parsed[param]:
42+
value = parsed[param][0] # Take first value
43+
os.environ[env_var] = value
44+
45+
# Reset config to pick up new environment variables
46+
reset_config()
47+
48+
49+
def main() -> None:
50+
"""
51+
Main entry point for Smithery deployment.
52+
53+
Starts HTTP server listening on PORT environment variable.
54+
Uses existing lazy initialization system.
55+
"""
56+
print("🔗 LinkedIn MCP Server (Smithery) 🔗")
57+
print("=" * 40)
58+
59+
# Get PORT from environment (Smithery requirement)
60+
port = int(os.environ.get("PORT", 8000))
61+
62+
# Set up environment for Smithery (can be called with query params later)
63+
# For now, just ensure we're in the right mode
64+
os.environ["DEBUG"] = os.environ.get("DEBUG", "false")
65+
66+
# Force HTTP transport and container-friendly settings
67+
os.environ.setdefault("TRANSPORT", "streamable-http")
68+
69+
# Get configuration (will use lazy_init=True by default)
70+
config = get_config()
71+
72+
# Configure logging
73+
log_level = logging.DEBUG if config.server.debug else logging.ERROR
74+
logging.basicConfig(
75+
level=log_level,
76+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
77+
)
78+
79+
logger = logging.getLogger("linkedin_mcp_server")
80+
logger.info(f"Starting Smithery MCP server on port {port}")
81+
82+
# Initialize driver (will use lazy init by default - perfect for Smithery!)
83+
initialize_driver()
84+
85+
# Create MCP server (tools will be available for discovery)
86+
mcp = create_mcp_server()
87+
88+
# Start HTTP server
89+
print("\n🚀 Running LinkedIn MCP server (Smithery HTTP mode)...")
90+
print(f"📡 HTTP server listening on http://0.0.0.0:{port}/mcp")
91+
print("🔧 Tools available for discovery - credentials validated on use")
92+
93+
try:
94+
mcp.run(transport="streamable-http", host="0.0.0.0", port=port, path="/mcp")
95+
except KeyboardInterrupt:
96+
print("\n👋 Shutting down LinkedIn MCP server...")
97+
shutdown_handler()
98+
except Exception as e:
99+
print(f"❌ Error running MCP server: {e}")
100+
shutdown_handler()
101+
raise
102+
103+
104+
if __name__ == "__main__":
105+
main()

0 commit comments

Comments
 (0)