Fix stdio bugs (#608)

1. Updates `arcade configure claude --from-local` to create a valid json
config for claude desktop. NOTE: The `arcade configure` command needs
some re-work. It's fragile.
2. Fixes bug where stdio servers were sending logs to the wrong sink.
3. Disabled colorized logs for stdio.
4. Added missing dependency `httpx` for servers created with `arcade
new`

## Claude Desktop json configuration for stdio
Personally I like option 1 because the configuration looks the simplest
### Option 1:
Equivalent to `python server.py stdio`
```
{
  "globalShortcut": "Alt+Ctrl+Space",
  "mcpServers": {
    "my_server": {
      "command": "/path/to/my/mcp/server/directory/.venv/bin/python",
      "args": [
        "/path/to/my/mcp/server/directory/server.py",
        "stdio"
      ]
    }
  }
}
```
### Option 2:
Equivalent to `uv run server.py stdio`
```
{
  "mcpServers": {
    "my_server": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/path/to/my/mcp/server/directory",
        "python",
        "server.py",
        "stdio"
      ]
    }
  }
}
```
### Option 3:
Equivalent to `python -m arcade_mcp_server stdio --cwd ./`
```
{
  "mcpServers": {
    "my_server": {
      "command": "/path/to/my/mcp/server/directory/.venv/bin/python",
      "args": [
        "-m",
        "arcade_mcp_server",
        "stdio",
        "--cwd",
        "/path/to/my/mcp/server/directory"
      ]
    }
  }
}
```
This commit is contained in:
Eric Gustin 2025-10-07 18:53:53 -07:00 committed by GitHub
parent 0cf1a8bd22
commit b780e5b807
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 48 additions and 18 deletions

View file

@ -56,6 +56,26 @@ def configure_claude_local(server_name: str, port: int = 8000, path: Path | None
config_path = path or get_claude_config_path()
config_path.parent.mkdir(parents=True, exist_ok=True)
# Assume server.py is the entry point for the server
server_file = Path.cwd() / "server.py"
# Find the Python interpreter in the virtual environment
venv_python = None
# Check for .venv first (uv default)
if (Path.cwd() / ".venv").exists():
system = platform.system()
if system == "Windows":
venv_python = Path.cwd() / ".venv" / "Scripts" / "python.exe"
else:
venv_python = Path.cwd() / ".venv" / "bin" / "python"
# Fall back to system python if no venv found
if not venv_python or not venv_python.exists():
console.print("[yellow]Warning: No .venv found, using system python[/yellow]")
import sys
venv_python = Path(sys.executable)
# Load existing config or create new one
config = {}
if config_path.exists():
@ -66,10 +86,10 @@ def configure_claude_local(server_name: str, port: int = 8000, path: Path | None
if "mcpServers" not in config:
config["mcpServers"] = {}
# Claude Desktop uses stdio transport
config["mcpServers"][server_name] = {
"command": "python",
"args": ["-m", "arcade_mcp_server", "stream"],
"url": f"http://localhost:{port}/mcp",
"command": str(venv_python),
"args": [str(server_file), "stdio"],
}
# Write updated config
@ -82,7 +102,8 @@ def configure_claude_local(server_name: str, port: int = 8000, path: Path | None
)
config_file_path = config_path.as_posix().replace(" ", "\\ ")
console.print(f" MCP client config file: {config_file_path}", style="dim")
console.print(f" MCP Server URL: http://localhost:{port}/mcp", style="dim")
console.print(f" Server file: {server_file}", style="dim")
console.print(f" Python interpreter: {venv_python}", style="dim")
console.print(" Restart Claude Desktop for changes to take effect.", style="yellow")

View file

@ -19,14 +19,14 @@ try:
ARCADE_MCP_MAX_VERSION = str(int(ARCADE_MCP_MIN_VERSION.split(".")[0]) + 1) + ".0.0"
except Exception as e:
console.print(f"[red]Failed to get arcade-mcp version: {e}[/red]")
ARCADE_MCP_MIN_VERSION = "1.0.0" # Default version if unable to fetch
ARCADE_MCP_MIN_VERSION = "1.1.0" # Default version if unable to fetch
ARCADE_MCP_MAX_VERSION = "2.0.0"
ARCADE_TDK_MIN_VERSION = "3.0.0"
ARCADE_TDK_MAX_VERSION = "4.0.0"
ARCADE_SERVE_MIN_VERSION = "3.0.0"
ARCADE_SERVE_MAX_VERSION = "4.0.0"
ARCADE_MCP_SERVER_MIN_VERSION = "1.0.1"
ARCADE_MCP_SERVER_MIN_VERSION = "1.1.1"
ARCADE_MCP_SERVER_MAX_VERSION = "2.0.0"

View file

@ -9,6 +9,7 @@ description = "MCP Server created with Arcade.dev"
requires-python = ">=3.10"
dependencies = [
"arcade-mcp-server>={{ arcade_mcp_server_min_version }},<{{ arcade_mcp_server_max_version }}",
"httpx>=0.28.0,<1.0.0",
]
[project.optional-dependencies]

View file

@ -54,7 +54,7 @@ def setup_logging(level: str = "INFO", stdio_mode: bool = False) -> None:
# Remove existing handlers
logger.remove()
# Configure output destination
# In stdio mode, use stderr (stdout is reserved for JSON-RPC)
sink = sys.stderr if stdio_mode else sys.stdout
# Add handler with appropriate format
@ -69,7 +69,7 @@ def setup_logging(level: str = "INFO", stdio_mode: bool = False) -> None:
sink,
format=format_str,
level=level,
colorize=True,
colorize=(not stdio_mode),
diagnose=(level == "DEBUG"),
)

View file

@ -103,7 +103,8 @@ class MCPApp:
self.server: MCPServer | None = None
self._load_env()
self._setup_logging()
if not logger._core.handlers: # type: ignore[attr-defined]
self._setup_logging(transport == "stdio")
# Properties (exposed below initializer)
@property
@ -128,17 +129,21 @@ class MCPApp:
load_dotenv(env_path, override=False)
logger.info(f"Loaded environment from {env_path}")
def _setup_logging(self) -> None:
def _setup_logging(self, stdio_mode: bool = False) -> None:
logger.remove()
# In stdio mode, use stderr (stdout is reserved for JSON-RPC)
sink = sys.stderr if stdio_mode else sys.stdout
if self.log_level == "DEBUG":
format_str = "<level>{level: <8}</level> | <green>{time:HH:mm:ss}</green> | <cyan>{name}:{line}</cyan> | <level>{message}</level>"
else:
format_str = "<level>{level: <8}</level> | <green>{time:HH:mm:ss}</green> | <level>{message}</level>"
logger.add(
sys.stdout,
sink,
format=format_str,
level=self.log_level,
colorize=True,
colorize=(not stdio_mode),
diagnose=(self.log_level == "DEBUG"),
)
@ -209,10 +214,13 @@ class MCPApp:
logger.error("No tools added to the server. Use @app.tool decorator or app.add_tool().")
sys.exit(1)
logger.info(f"Starting {self.name} v{self.version} with {len(self._catalog)} tools")
host, port, transport = MCPApp._get_configuration_overrides(host, port, transport)
# Since the transport could have changed since __init__, we need to setup logging again
self._setup_logging(transport == "stdio")
logger.info(f"Starting {self.name} v{self.version} with {len(self._catalog)} tools")
if transport in ["http", "streamable-http", "streamable"]:
run_arcade_mcp(
catalog=self._catalog,

View file

@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "arcade-mcp-server"
version = "1.1.0"
version = "1.1.1"
description = "Model Context Protocol (MCP) server framework for Arcade.dev"
readme = "README.md"
authors = [{ name = "Arcade.dev" }]

View file

@ -1,6 +1,6 @@
[project]
name = "arcade-mcp"
version = "1.0.2"
version = "1.1.0"
description = "Arcade.dev - Tool Calling platform for Agents"
readme = "README.md"
license = {file = "LICENSE"}
@ -21,7 +21,7 @@ requires-python = ">=3.10"
dependencies = [
# CLI dependencies
"arcade-mcp-server>=1.1.0,<2.0.0",
"arcade-mcp-server>=1.1.1,<2.0.0",
"arcade-core>=3.0.0,<4.0.0",
"typer==0.10.0",
"rich==13.9.4",
@ -42,7 +42,7 @@ all = [
"pytz>=2024.1",
"python-dateutil>=2.8.2",
# mcp
"arcade-mcp-server>=1.1.0,<2.0.0",
"arcade-mcp-server>=1.1.1,<2.0.0",
# serve
"arcade-serve>=3.0.0,<4.0.0",
# tdk