Skip to content

Commit 8082f30

Browse files
committed
fix(smithery): url query parsing and created proper ASGI application using FastMCP's http_app()
1 parent c0d6e37 commit 8082f30

File tree

10 files changed

+538
-212
lines changed

10 files changed

+538
-212
lines changed

Dockerfile.smithery

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ COPY . /app
2020
RUN --mount=type=cache,target=/root/.cache/uv \
2121
uv sync --frozen
2222

23+
# Set ChromeDriver path for Alpine
24+
ENV CHROMEDRIVER_PATH=/usr/bin/chromedriver
25+
26+
# Set environment variables for Smithery
27+
ENV LAZY_INIT=true \
28+
NON_INTERACTIVE=true \
29+
HEADLESS=true \
30+
TRANSPORT=streamable-http \
31+
DEBUG=false
32+
2333
# Create a non-root user
2434
RUN adduser -D -u 1000 mcpuser && chown -R mcpuser:mcpuser /app
2535
USER mcpuser

SMITHERY_FIX_SUMMARY.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Smithery Deployment Fix Summary
2+
3+
## Issues Identified and Fixed
4+
5+
### 1. **Tool Discovery Failure**
6+
- **Problem**: Smithery couldn't scan tools from the server, getting "TypeError: fetch failed"
7+
- **Root Cause**: Server wasn't properly handling HTTP requests for tool discovery
8+
- **Fix**: Created proper ASGI app using FastMCP's `http_app()` method with middleware support
9+
10+
### 2. **Configuration Handling**
11+
- **Problem**: Server expected environment variables, but Smithery passes config as query parameters
12+
- **Root Cause**: Misunderstanding of how Smithery passes configuration
13+
- **Fix**: Implemented Starlette middleware to extract query parameters and update environment
14+
15+
### 3. **Middleware Implementation**
16+
- **Problem**: Initial attempt used incorrect FastMCP middleware API
17+
- **Root Cause**: Used `@mcp.middleware()` decorator which doesn't exist
18+
- **Fix**: Used proper Starlette middleware passed to `http_app()` method
19+
20+
### 4. **Server Startup**
21+
- **Problem**: Server needed to start without credentials for tool discovery
22+
- **Root Cause**: Lazy initialization wasn't properly configured
23+
- **Fix**: Ensured all environment variables are set for lazy init before imports
24+
25+
## Key Changes Made
26+
27+
### 1. **smithery_main.py**
28+
```python
29+
# Proper ASGI app creation with middleware
30+
def create_app():
31+
mcp = create_mcp_server()
32+
middleware = [Middleware(SmitheryConfigMiddleware)]
33+
app = mcp.http_app(path="/mcp", middleware=middleware, transport="streamable-http")
34+
return app
35+
36+
# Use uvicorn to run the ASGI app
37+
uvicorn.run(app, host="0.0.0.0", port=port)
38+
```
39+
40+
### 2. **Configuration Updates**
41+
- Updated `loaders.py` to support `LAZY_INIT` and `NON_INTERACTIVE` env vars
42+
- Made credentials optional in `smithery.yaml` for tool discovery
43+
44+
### 3. **Dockerfile.smithery**
45+
- Added `CHROMEDRIVER_PATH` environment variable
46+
- Set all required environment variables for Smithery mode
47+
48+
## How Smithery Integration Works
49+
50+
1. **Tool Discovery Phase**:
51+
- Smithery sends requests to `/mcp` without credentials
52+
- Server must respond with available tools list
53+
- No Chrome driver or authentication needed
54+
55+
2. **Tool Execution Phase**:
56+
- Smithery passes credentials as query parameters: `/mcp?linkedin_email=...&linkedin_password=...`
57+
- Middleware extracts these and updates environment
58+
- Chrome driver is initialized only when tools are actually called
59+
60+
3. **Configuration Flow**:
61+
```
62+
Smithery UI → Query Parameters → Middleware → Environment Variables → Config Reset → Tool Execution
63+
```
64+
65+
## Testing Commands
66+
67+
```bash
68+
# Local testing
69+
chmod +x test_local_smithery.sh
70+
./test_local_smithery.sh
71+
72+
# Manual server start
73+
PORT=8000 uv run python smithery_main.py
74+
75+
# Test tool discovery
76+
curl -X POST http://localhost:8000/mcp \
77+
-H "Content-Type: application/json" \
78+
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
79+
```
80+
81+
## Deployment Steps
82+
83+
1. **Commit and push changes**:
84+
```bash
85+
git add -A
86+
git commit -m "Fix Smithery deployment - proper query param handling and lazy init"
87+
git push origin feat/smithery-http-transport
88+
```
89+
90+
2. **Monitor Smithery deployment**:
91+
- Check Docker build succeeds
92+
- Verify "Tool scanning" passes
93+
- Test connection with credentials
94+
95+
## Key Principles
96+
97+
1. **Lazy Loading**: No resources initialized until needed
98+
2. **Query Parameter Config**: Smithery passes config via URL params, not env vars
99+
3. **ASGI Application**: Use FastMCP's `http_app()` for proper HTTP handling
100+
4. **Middleware**: Use Starlette middleware for HTTP request processing
101+
5. **Non-Interactive**: No prompts or user input in container environment
102+
103+
## Troubleshooting
104+
105+
If deployment still fails:
106+
1. Check Smithery logs for specific errors
107+
2. Ensure ChromeDriver is available at `/usr/bin/chromedriver` in container
108+
3. Verify all Python dependencies are installed
109+
4. Test locally with `test_local_smithery.sh` first

linkedin_mcp_server/config/loaders.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ def load_from_env(config: AppConfig) -> AppConfig:
4444
# Headless mode
4545
if os.environ.get("HEADLESS") in ("0", "false", "False", "no", "No"):
4646
config.chrome.headless = False
47+
elif os.environ.get("HEADLESS") in ("1", "true", "True", "yes", "Yes"):
48+
config.chrome.headless = True
49+
50+
# Non-interactive mode
51+
if os.environ.get("NON_INTERACTIVE") in ("1", "true", "True", "yes", "Yes"):
52+
config.chrome.non_interactive = True
53+
54+
# Lazy initialization
55+
if os.environ.get("LAZY_INIT") in ("1", "true", "True", "yes", "Yes"):
56+
config.server.lazy_init = True
57+
elif os.environ.get("LAZY_INIT") in ("0", "false", "False", "no", "No"):
58+
config.server.lazy_init = False
4759

4860
return config
4961

smithery.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ startCommand:
1414
type: "string"
1515
description: "LinkedIn password for authentication"
1616
sensitive: true
17-
required: ["linkedin_email", "linkedin_password"]
17+
required: [] # Make them optional to allow tool discovery without credentials
1818
exampleConfig:
1919
linkedin_email: "user@example.com"
2020
linkedin_password: "password123"

0 commit comments

Comments
 (0)