diff --git a/Dockerfile b/Dockerfile index 258642b..0733903 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,4 +12,38 @@ RUN chmod +x entrypoint.py ENV PYTHONPATH=/app/src:$PYTHONPATH +# Polygon MCP Server Configuration +# These environment variables can be overridden at runtime + +# Required - Must be provided at runtime +# ENV POLYGON_API_KEY=your_api_key_here + +# Transport Configuration +# Options: stdio (default), sse, streamable-http +ENV MCP_TRANSPORT=stdio + +# Server Settings +# Enable debug mode (true/false) +ENV MCP_DEBUG=false +# Logging level: DEBUG, INFO, WARNING, ERROR, CRITICAL +ENV MCP_LOG_LEVEL=INFO + +# HTTP Settings (used for SSE and Streamable-HTTP transports) +# Host/IP address to bind to (use 0.0.0.0 for all interfaces) +ENV MCP_HOST=127.0.0.1 +# Port to bind to +ENV MCP_PORT=8000 + +# SSE-Specific Settings (only used when MCP_TRANSPORT=sse) +# Mount path for the application (e.g., "/api", "/github") +ENV MCP_MOUNT_PATH=/ +# SSE endpoint path +ENV MCP_SSE_PATH=/sse +# Message endpoint path +ENV MCP_MESSAGE_PATH=/messages/ + +# Streamable-HTTP-Specific Settings (only used when MCP_TRANSPORT=streamable-http) +# Streamable HTTP endpoint path +ENV MCP_STREAMABLE_HTTP_PATH=/mcp + ENTRYPOINT ["uv", "run", "./entrypoint.py"] diff --git a/ENV_VARIABLES.md b/ENV_VARIABLES.md new file mode 100644 index 0000000..e74ecc1 --- /dev/null +++ b/ENV_VARIABLES.md @@ -0,0 +1,158 @@ +# Environment Variables + +The Polygon MCP server can be configured using the following environment variables: + +## Required + +- `POLYGON_API_KEY`: Your Polygon.io API key (required for API access) + +## Transport Configuration + +- `MCP_TRANSPORT`: Transport protocol to use + - `stdio` (default) - Standard input/output + - `sse` - Server-Sent Events + - `streamable-http` - Streamable HTTP + +## Server Settings + +- `MCP_DEBUG`: Enable debug mode + - Values: `true`, `1`, `yes`, `on` (enables debug) + - Default: `false` + +- `MCP_LOG_LEVEL`: Logging level + - Values: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` + - Default: `INFO` + +## HTTP Settings (for SSE and Streamable-HTTP transports) + +- `MCP_HOST`: Host/IP address to bind to + - Default: `127.0.0.1` + - Example: `0.0.0.0` (bind to all interfaces) + +- `MCP_PORT`: Port to bind to + - Default: `8000` + - Example: `9000` + +## SSE-Specific Settings + +These settings only apply when using the SSE transport: + +- `MCP_MOUNT_PATH`: Mount path for the application + - Default: `/` + - Example: `/github` + +- `MCP_SSE_PATH`: SSE endpoint path + - Default: `/sse` + +- `MCP_MESSAGE_PATH`: Message endpoint path + - Default: `/messages/` + +## Streamable-HTTP-Specific Settings + +These settings only apply when using the Streamable-HTTP transport: + +- `MCP_STREAMABLE_HTTP_PATH`: Streamable HTTP endpoint path + - Default: `/mcp` + +- `MCP_JSON_RESPONSE`: Return plain JSON instead of JSONRPC format + - Values: `true`, `1`, `yes`, `on` (enables JSON response mode) + - Default: `false` + - When enabled, returns raw JSON responses instead of JSONRPC wrapped responses + +- `MCP_STATELESS_HTTP`: Use stateless mode (new transport per request) + - Values: `true`, `1`, `yes`, `on` (enables stateless mode) + - Default: `false` + - When enabled, creates a new transport connection for each request instead of maintaining state + +## Example Usage + +### Running with stdio transport (default) +```bash +export POLYGON_API_KEY=your_api_key_here +uv run entrypoint.py +``` + +### Running with SSE transport +```bash +export POLYGON_API_KEY=your_api_key_here +export MCP_TRANSPORT=sse +export MCP_HOST=0.0.0.0 +export MCP_PORT=9000 +export MCP_LOG_LEVEL=DEBUG +uv run entrypoint.py +``` + +### Running with Streamable-HTTP transport +```bash +export POLYGON_API_KEY=your_api_key_here +export MCP_TRANSPORT=streamable-http +export MCP_HOST=0.0.0.0 +export MCP_PORT=3000 +export MCP_STREAMABLE_HTTP_PATH=/api/mcp +uv run entrypoint.py +``` + +### Running with Streamable-HTTP in JSON response mode +```bash +export POLYGON_API_KEY=your_api_key_here +export MCP_TRANSPORT=streamable-http +export MCP_HOST=0.0.0.0 +export MCP_PORT=3000 +export MCP_JSON_RESPONSE=true +export MCP_STATELESS_HTTP=true +uv run entrypoint.py +``` + +### Docker Examples + +#### Building with custom defaults in Dockerfile +```dockerfile +ENV POLYGON_API_KEY=your_api_key_here +ENV MCP_TRANSPORT=streamable-http +ENV MCP_HOST=0.0.0.0 +ENV MCP_PORT=8080 +ENV MCP_LOG_LEVEL=INFO +ENV MCP_DEBUG=false +``` + +#### Running with docker run +```bash +# Basic run with API key +docker run -e POLYGON_API_KEY=your_api_key_here mcp-polygon + +# Run with SSE transport on all interfaces +docker run -e POLYGON_API_KEY=your_api_key_here \ + -e MCP_TRANSPORT=sse \ + -e MCP_HOST=0.0.0.0 \ + -e MCP_PORT=9000 \ + -p 9000:9000 \ + mcp-polygon + +# Run with Streamable-HTTP and debug logging +docker run -e POLYGON_API_KEY=your_api_key_here \ + -e MCP_TRANSPORT=streamable-http \ + -e MCP_HOST=0.0.0.0 \ + -e MCP_LOG_LEVEL=DEBUG \ + -e MCP_STREAMABLE_HTTP_PATH=/api/mcp \ + -p 8000:8000 \ + mcp-polygon + +# Run with Streamable-HTTP in JSON response and stateless mode +docker run -e POLYGON_API_KEY=your_api_key_here \ + -e MCP_TRANSPORT=streamable-http \ + -e MCP_HOST=0.0.0.0 \ + -e MCP_JSON_RESPONSE=true \ + -e MCP_STATELESS_HTTP=true \ + -p 8000:8000 \ + mcp-polygon +``` + +#### Using docker-compose +See [docker-compose.yml](docker-compose.yml) for a complete example with all configurable environment variables. + +## Notes + +- The `host` and `port` settings are only used for HTTP-based transports (SSE and Streamable-HTTP) +- When using `stdio` transport, HTTP settings are ignored +- Path settings are transport-specific and only apply to their respective transports +- Debug mode and log level apply to all transport types diff --git a/README.md b/README.md index 418d4c6..40197c3 100644 --- a/README.md +++ b/README.md @@ -96,16 +96,68 @@ Make sure you complete the various fields. ``` -## Transport Configuration +## Configuration -By default, STDIO transport is used. +The Polygon MCP server can be configured using environment variables. See [ENV_VARIABLES.md](ENV_VARIABLES.md) for complete documentation. -To configure [SSE](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse) or [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http), set the `MCP_TRANSPORT` environment variable. +### Environment Variables -Example: +#### Required +- `POLYGON_API_KEY` - Your Polygon.io API key +#### Transport Configuration +- `MCP_TRANSPORT` - Transport protocol (`stdio`, `sse`, `streamable-http`). Default: `stdio` + +#### Server Settings +- `MCP_DEBUG` - Enable debug mode (`true`/`false`). Default: `false` +- `MCP_LOG_LEVEL` - Logging level (`DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`). Default: `INFO` + +#### HTTP Settings (for SSE and Streamable-HTTP) +- `MCP_HOST` - Host/IP to bind to. Default: `127.0.0.1` +- `MCP_PORT` - Port to bind to. Default: `8000` + +#### Path Settings +- `MCP_MOUNT_PATH` - Mount path for SSE. Default: `/` +- `MCP_SSE_PATH` - SSE endpoint path. Default: `/sse` +- `MCP_MESSAGE_PATH` - Message endpoint for SSE. Default: `/messages/` +- `MCP_STREAMABLE_HTTP_PATH` - Streamable HTTP endpoint. Default: `/mcp` + +#### Streamable HTTP Settings +- `MCP_JSON_RESPONSE` - Return plain JSON instead of JSONRPC (`true`/`false`). Default: `false` +- `MCP_STATELESS_HTTP` - Use stateless mode, new transport per request (`true`/`false`). Default: `false` + +### Examples + +Default STDIO transport: +```bash +POLYGON_API_KEY= \ +uv run entrypoint.py +``` + +SSE transport with custom host/port: +```bash +MCP_TRANSPORT=sse \ +MCP_HOST=0.0.0.0 \ +MCP_PORT=9000 \ +POLYGON_API_KEY= \ +uv run entrypoint.py +``` + +Streamable HTTP with debug logging: ```bash MCP_TRANSPORT=streamable-http \ +MCP_LOG_LEVEL=DEBUG \ +MCP_HOST=0.0.0.0 \ +POLYGON_API_KEY= \ +uv run entrypoint.py +``` + +Streamable HTTP with JSON response and stateless mode: +```bash +MCP_TRANSPORT=streamable-http \ +MCP_JSON_RESPONSE=true \ +MCP_STATELESS_HTTP=true \ +MCP_HOST=0.0.0.0 \ POLYGON_API_KEY= \ uv run entrypoint.py ``` @@ -146,10 +198,20 @@ Check to ensure you have the [Prerequisites](#prerequisites) installed. # Sync dependencies uv sync -# Run the server +# Run the server with default settings (stdio transport) POLYGON_API_KEY=your_api_key_here uv run mcp_polygon + +# Or run with custom configuration +MCP_TRANSPORT=streamable-http \ +MCP_HOST=0.0.0.0 \ +MCP_PORT=8080 \ +MCP_LOG_LEVEL=DEBUG \ +POLYGON_API_KEY=your_api_key_here \ +uv run entrypoint.py ``` +See [ENV_VARIABLES.md](ENV_VARIABLES.md) for all available configuration options. +
Local Dev Config for claude_desktop_config.json diff --git a/docker-compose.yml b/docker-compose.yml index 2dc3ac2..4097b1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,26 @@ services: - .:/app container_name: mcp_polygon_server environment: + # Required - POLYGON_API_KEY=${POLYGON_API_KEY} + # Transport configuration + - MCP_TRANSPORT=${MCP_TRANSPORT:-stdio} + # Server settings + - MCP_DEBUG=${MCP_DEBUG:-false} + - MCP_LOG_LEVEL=${MCP_LOG_LEVEL:-INFO} + # HTTP settings (for SSE and Streamable-HTTP) + - MCP_HOST=${MCP_HOST:-127.0.0.1} + - MCP_PORT=${MCP_PORT:-8000} + # SSE-specific settings + - MCP_MOUNT_PATH=${MCP_MOUNT_PATH:-/} + - MCP_SSE_PATH=${MCP_SSE_PATH:-/sse} + - MCP_MESSAGE_PATH=${MCP_MESSAGE_PATH:-/messages/} + # Streamable-HTTP-specific settings + - MCP_STREAMABLE_HTTP_PATH=${MCP_STREAMABLE_HTTP_PATH:-/mcp} + - MCP_JSON_RESPONSE=${MCP_JSON_RESPONSE:-false} + - MCP_STATELESS_HTTP=${MCP_STATELESS_HTTP:-false} stdin_open: true tty: true + # Expose port for HTTP-based transports + ports: + - "${MCP_PORT:-8000}:${MCP_PORT:-8000}" diff --git a/entrypoint.py b/entrypoint.py index 6ca7314..cab0fc6 100644 --- a/entrypoint.py +++ b/entrypoint.py @@ -1,6 +1,6 @@ #!/usr/bin/env python import os -from typing import Literal +from typing import Literal, Optional, Dict, Any from mcp_polygon import server @@ -21,6 +21,64 @@ def transport() -> Literal["stdio", "sse", "streamable-http"]: return supported_transports.get(mcp_transport_str, "stdio") +def get_server_settings() -> Dict[str, Any]: + """ + Get all server settings from environment variables. + Returns a dictionary with all FastMCP server settings. + """ + settings = {} + + # HTTP settings + # Host/IP address to bind to + settings["host"] = os.environ.get("MCP_HOST", "127.0.0.1") + + # Port to bind to + port_str = os.environ.get("MCP_PORT", "8000") + try: + settings["port"] = int(port_str) + except ValueError: + print(f"Warning: Invalid MCP_PORT value '{port_str}', using default port 8000") + settings["port"] = 8000 + + # Server settings + # Debug mode + debug_str = os.environ.get("MCP_DEBUG", "false") + settings["debug"] = debug_str.lower() in ["true", "1", "yes", "on"] + + # Log level + log_level = os.environ.get("MCP_LOG_LEVEL", "INFO").upper() + valid_log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] + if log_level in valid_log_levels: + settings["log_level"] = log_level + else: + print(f"Warning: Invalid MCP_LOG_LEVEL '{log_level}', using default 'INFO'") + settings["log_level"] = "INFO" + + # HTTP path settings + # Mount path (e.g., "/github", defaults to root path) + settings["mount_path"] = os.environ.get("MCP_MOUNT_PATH", "/") + + # SSE endpoint path + settings["sse_path"] = os.environ.get("MCP_SSE_PATH", "/sse") + + # Message endpoint path (for SSE) + settings["message_path"] = os.environ.get("MCP_MESSAGE_PATH", "/messages/") + + # Streamable HTTP endpoint path + settings["streamable_http_path"] = os.environ.get("MCP_STREAMABLE_HTTP_PATH", "/mcp") + + # Streamable HTTP specific settings + # JSON response mode (returns JSON instead of JSONRPC) + json_response_str = os.environ.get("MCP_JSON_RESPONSE", "false") + settings["json_response"] = json_response_str.lower() in ["true", "1", "yes", "on"] + + # Stateless HTTP mode (new transport per request) + stateless_http_str = os.environ.get("MCP_STATELESS_HTTP", "false") + settings["stateless_http"] = stateless_http_str.lower() in ["true", "1", "yes", "on"] + + return settings + + # Ensure the server process doesn't exit immediately when run as an MCP server def start_server(): polygon_api_key = os.environ.get("POLYGON_API_KEY", "") @@ -29,7 +87,27 @@ def start_server(): else: print("Starting Polygon MCP server with API key configured.") - server.run(transport=transport()) + transport_type = transport() + settings = get_server_settings() + + # Log configuration for HTTP-based transports + if transport_type in ["sse", "streamable-http"]: + print(f"Server configuration for {transport_type} transport:") + print(f" Host: {settings['host']}") + print(f" Port: {settings['port']}") + print(f" Debug: {settings['debug']}") + print(f" Log Level: {settings['log_level']}") + + if transport_type == "sse": + print(f" Mount Path: {settings['mount_path']}") + print(f" SSE Path: {settings['sse_path']}") + print(f" Message Path: {settings['message_path']}") + elif transport_type == "streamable-http": + print(f" Streamable HTTP Path: {settings['streamable_http_path']}") + print(f" JSON Response Mode: {settings['json_response']}") + print(f" Stateless HTTP Mode: {settings['stateless_http']}") + + server.run(transport=transport_type, **settings) if __name__ == "__main__": diff --git a/src/mcp_polygon/server.py b/src/mcp_polygon/server.py index 766d454..2646e80 100644 --- a/src/mcp_polygon/server.py +++ b/src/mcp_polygon/server.py @@ -2053,6 +2053,53 @@ async def get_futures_snapshot( # It will be run from entrypoint.py -def run(transport: Literal["stdio", "sse", "streamable-http"] = "stdio") -> None: - """Run the Polygon MCP server.""" +def run( + transport: Literal["stdio", "sse", "streamable-http"] = "stdio", + host: str = "127.0.0.1", + port: int = 8000, + debug: bool = False, + log_level: str = "INFO", + mount_path: str = "/", + sse_path: str = "/sse", + message_path: str = "/messages/", + streamable_http_path: str = "/mcp", + json_response: bool = False, + stateless_http: bool = False, +) -> None: + """Run the Polygon MCP server. + + Args: + transport: Transport protocol to use ("stdio", "sse", or "streamable-http") + host: Host/IP address to bind to for HTTP transports (sse, streamable-http) + port: Port to bind to for HTTP transports (sse, streamable-http) + debug: Enable debug mode + log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) + mount_path: Mount path for SSE transport (e.g., "/github", defaults to root path) + sse_path: SSE endpoint path + message_path: Message endpoint path (for SSE) + streamable_http_path: Streamable HTTP endpoint path + json_response: Return JSON instead of JSONRPC for Streamable HTTP (default: False) + stateless_http: Use stateless mode (new transport per request) for Streamable HTTP (default: False) + """ + # Update the FastMCP settings + poly_mcp.settings.debug = debug + poly_mcp.settings.log_level = log_level + + # Update HTTP-specific settings + if transport in ["sse", "streamable-http"]: + poly_mcp.settings.host = host + poly_mcp.settings.port = port + + # SSE-specific paths + if transport == "sse": + poly_mcp.settings.mount_path = mount_path + poly_mcp.settings.sse_path = sse_path + poly_mcp.settings.message_path = message_path + + # Streamable HTTP-specific path + elif transport == "streamable-http": + poly_mcp.settings.streamable_http_path = streamable_http_path + poly_mcp.settings.json_response = json_response + poly_mcp.settings.stateless_http = stateless_http + poly_mcp.run(transport)