Skip to content
Draft
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
18 changes: 16 additions & 2 deletions src/fleet_mcp/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,33 @@ def _load_config(ctx: click.Context) -> FleetConfig:
sys.exit(1)



@cli.command()
@click.option(
"--port",
"-p",
type=int,
default=None,
help="Port to listen on for HTTP transports (default: 8000, env: FLEET_MCP_PORT)",
show_default=False,
)
@click.pass_context
def run(ctx: click.Context) -> None:
def run(ctx: click.Context, port: int | None = None) -> None:
"""Run the Fleet MCP server."""
config = _load_config(ctx)

# Dynamically determine port: CLI > env > default
if port is None:
import os
port = int(os.environ.get("FLEET_MCP_PORT", 8000))

try:
server = FleetMCPServer(config)
readonly_status = " (READ-ONLY MODE)" if config.readonly else ""
click.echo(
f"Starting Fleet MCP Server for {config.server_url}{readonly_status}"
)
server.run()
server.run(port=port)
except KeyboardInterrupt:
click.echo("\nShutting down Fleet MCP Server...")
except Exception as e:
Expand Down
5 changes: 5 additions & 0 deletions src/fleet_mcp/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ class FleetConfig(BaseSettings):
description="Number of hours to retain completed async query results before cleanup",
)

transport: str = Field(
default="stdio",
description="MCP transport protocol: 'stdio', 'sse', or 'streamable-http'",
)

@field_validator("server_url")
@classmethod
def validate_server_url(cls, v: str) -> str:
Expand Down
25 changes: 20 additions & 5 deletions src/fleet_mcp/server.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"""Fleet MCP Server - Main MCP server implementation."""


import logging
import os
import argparse
from typing import Any

from mcp.server.fastmcp import FastMCP
Expand Down Expand Up @@ -442,16 +445,24 @@ async def _preload_schema_cache(self) -> None:
logger.warning(f"Failed to preload schema cache: {e}")
logger.info("Schema cache will be loaded on first use")

def run(self) -> None:
"""Run the MCP server."""
def run(self, port: int = 8000) -> None:
"""Run the MCP server, supporting custom port."""
import asyncio
import uvicorn

logger.info(f"Starting Fleet MCP Server for {self.config.server_url}")
logger.info(f"Transport: {self.config.transport}")
logger.info(f"Listening on port: {port}")

# Preload schema cache before starting server
asyncio.run(self._preload_schema_cache())

self.mcp.run()
# Only pass port for HTTP-based transports
if self.config.transport in ("sse", "streamable-http"):
app = self.mcp.sse_app() if self.config.transport == "sse" else self.mcp.streamable_http_app()
uvicorn.run(app, host="0.0.0.0", port=port, log_level="info")
else:
self.mcp.run(transport=self.config.transport)


def create_server(config: FleetConfig | None = None) -> FleetMCPServer:
Expand All @@ -467,16 +478,20 @@ def create_server(config: FleetConfig | None = None) -> FleetMCPServer:


def main() -> None:
"""Main entry point for Fleet MCP Server."""
"""Main entry point for Fleet MCP Server with port support."""
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)

parser = argparse.ArgumentParser(description="Fleet MCP Server")
parser.add_argument("--port", "-p", type=int, default=int(os.environ.get("FLEET_MCP_PORT", 8000)), help="Port to listen on for HTTP transports (default: 8000, env: FLEET_MCP_PORT)")
args, unknown = parser.parse_known_args()

try:
server = create_server()
server.run()
server.run(port=args.port)
except Exception as e:
logger.error(f"Failed to start Fleet MCP Server: {e}")
raise