Skip to content
Open
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
14 changes: 14 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.git
.gitignore
images/
docs/
__pycache__/
*.pyc
*.pyo
*.egg-info/
.venv/
venv/
env/
logs/
*.log
.DS_Store
36 changes: 36 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# syntax=docker/dockerfile:1
FROM python:3.12-slim AS builder

WORKDIR /app

# Install build dependencies (none needed for pure Python, but kept for compatibility)
RUN apt-get update && apt-get install -y --no-install-recommends gcc && rm -rf /var/lib/apt/lists/*

COPY pyproject.toml ./
COPY src/ ./src/

RUN pip install --no-cache-dir .

# ---- Runtime stage ----
FROM python:3.12-slim

WORKDIR /app

# Create non-root user with home directory
RUN groupadd -r appgroup && useradd -r -g appgroup -m -d /home/appuser appuser

# Copy installed packages and console script from builder
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin/grok-search /usr/local/bin/grok-search

# Default env for remote HTTP mode
ENV PYTHONUNBUFFERED=1
ENV MCP_TRANSPORT=http
ENV MCP_HOST=0.0.0.0
ENV MCP_PORT=8000

EXPOSE 8000

USER appuser

CMD ["grok-search"]
34 changes: 34 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
services:
grok-search:
build:
context: .
dockerfile: Dockerfile
container_name: grok-search
restart: unless-stopped
ports:
- "${MCP_PORT:-8000}:8000"
environment:
# Required
- GROK_API_URL=${GROK_API_URL}
- GROK_API_KEY=${GROK_API_KEY}
# Optional MCP transport settings
- MCP_TRANSPORT=${MCP_TRANSPORT:-http}
- MCP_HOST=0.0.0.0
- MCP_PORT=8000
- MCP_AUTH_TOKEN=${MCP_AUTH_TOKEN:-}
# Optional Grok settings
- GROK_MODEL=${GROK_MODEL:-grok-4-fast}
- GROK_DEBUG=${GROK_DEBUG:-false}
- GROK_LOG_LEVEL=${GROK_LOG_LEVEL:-INFO}
# Optional search providers
- TAVILY_ENABLED=${TAVILY_ENABLED:-true}
- TAVILY_API_URL=${TAVILY_API_URL:-https://api.tavily.com}
- TAVILY_API_KEY=${TAVILY_API_KEY:-}
- FIRECRAWL_API_URL=${FIRECRAWL_API_URL:-https://api.firecrawl.dev/v2}
- FIRECRAWL_API_KEY=${FIRECRAWL_API_KEY:-}
# Uncomment if you want switch_model persistence across restarts
# volumes:
# - grok-search-config:/home/appuser/.config/grok-search

# volumes:
# grok-search-config:
29 changes: 27 additions & 2 deletions src/grok_search/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,26 @@
from .planning import engine as planning_engine, _split_csv

import asyncio
import os

mcp = FastMCP("grok-search")
_mcp_auth_token = os.getenv("MCP_AUTH_TOKEN")
_auth = None
if _mcp_auth_token:
from fastmcp.server.auth.auth import TokenVerifier, AccessToken

class _StaticBearerAuth(TokenVerifier):
def __init__(self, token: str):
self._token = token
super().__init__()

async def verify_token(self, token: str) -> AccessToken | None:
if token == self._token:
return AccessToken(token=token, client_id="grok-search", scopes=[])
return None

_auth = _StaticBearerAuth(_mcp_auth_token)

mcp = FastMCP("grok-search", auth=_auth)

_SOURCES_CACHE = SourcesCache(max_size=256)
_AVAILABLE_MODELS_CACHE: dict[tuple[str, str], list[str]] = {}
Expand Down Expand Up @@ -844,6 +862,10 @@ def main():
import os
import threading

transport = os.getenv("MCP_TRANSPORT", "stdio").strip().lower()
host = os.getenv("MCP_HOST", "127.0.0.1")
port = int(os.getenv("MCP_PORT", "8000"))

# 信号处理(仅主线程)
if threading.current_thread() is threading.main_thread():
def handle_shutdown(signum, frame):
Expand Down Expand Up @@ -880,7 +902,10 @@ def monitor_parent():
threading.Thread(target=monitor_parent, daemon=True).start()

try:
mcp.run(transport="stdio", show_banner=False)
if transport in {"http", "streamable-http", "sse"}:
mcp.run(transport=transport, host=host, port=port, show_banner=False)
else:
mcp.run(transport="stdio", show_banner=False)
except KeyboardInterrupt:
pass
finally:
Expand Down