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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies = [
"anaconda-cli-base>=0.8.1,<1.0.0",
"click>=8.2.0,<9.0.0",
"platformdirs>=4.0.0,<5",
"conda-mcp-lite",
"rich>=13.0.0"
]

Expand Down
14 changes: 12 additions & 2 deletions src/anaconda_mcp/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from pathlib import Path

import click
from anaconda_anon_usage.tokens import client_token
from mcp_compose.cli import (
compose_command as _compose,
)
Expand Down Expand Up @@ -47,6 +46,17 @@

logger = logging.getLogger(__name__)

_AAU_TOKEN_PATH = Path.home() / ".conda" / "aau_token"


def _read_client_token() -> str:
try:
if _AAU_TOKEN_PATH.is_file():
return _AAU_TOKEN_PATH.read_text().strip().split()[0]
except OSError:
pass
return ""


def _send_install_event():
try:
Expand Down Expand Up @@ -117,7 +127,7 @@ def _handle_sigterm(signum, frame):
snake_eyes = SnakeEyes()
start_login(lambda x: x)
active_user_params: dict[str, str] = {}
aau = client_token()
aau = _read_client_token()
if aau:
active_user_params["aau_client_id"] = aau
snake_eyes.send(
Expand Down
30 changes: 13 additions & 17 deletions src/anaconda_mcp/mcp_compose.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,26 @@ domain = "anaconda.com"
# ============================================================================

# ============================================================================
# Proxied Streamable HTTP MCP Servers
# Proxied STDIO MCP Servers
# ============================================================================
[[servers.proxied.streamable-http]]
[[servers.proxied.stdio]]
name = "conda"
url = "http://localhost:4041/mcp"
command = ["python", "-m", "conda_mcp_lite"]
restart_policy = "on-failure"
max_restarts = 3

# ============================================================================
# Proxied Streamable HTTP MCP Servers (remote, no auto_start)
# ============================================================================
[[servers.proxied.streamable-http]]
name = "search"
url = "https://stage.anaconda.com/api/search/mcp"
timeout = 30
keep_alive = true
reconnect_on_failure = true
max_reconnect_attempts = 10
health_check_enabled = false
mode = "proxy"
auto_start = true
command = ["python", "-m", "environments_mcp_server", "start", "--transport", "streamable-http", "--port", "4041"]
startup_delay = 3


# ============================================================================
# Tool Manager Configuration
Expand All @@ -74,13 +79,4 @@ version_suffix_format = "_v{version}"
# REST API Configuration
# ============================================================================
[api]
enabled = true
path_prefix = "/api/v1"
host = "0.0.0.0"
port = 8888
cors_enabled = true
cors_origins = ["http://localhost:3000"]
cors_methods = ["GET", "POST", "PUT", "DELETE"]
docs_enabled = true
docs_path = "/docs"
openapi_path = "/openapi.json"
enabled = false
32 changes: 15 additions & 17 deletions src/anaconda_mcp/mcp_compose.toml.template
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,28 @@ domain = "anaconda.com"
# ============================================================================

# ============================================================================
# Proxied Streamable HTTP MCP Servers
# Proxied STDIO MCP Servers
# ============================================================================
[[servers.proxied.streamable-http]]
[[servers.proxied.stdio]]
name = "conda"
url = "http://localhost:4041/mcp"
command = ["{{PYTHON_EXECUTABLE}}", "-m", "conda_mcp_lite"]
restart_policy = "on-failure"
max_restarts = 3

# ============================================================================
# Proxied Streamable HTTP MCP Servers (remote, no auto_start)
# ============================================================================
[[servers.proxied.streamable-http]]
name = "search"
url = "https://stage.anaconda.com/api/search/mcp"
auth_token = "{{ANACONDA_TOKEN}}"
auth_type = "bearer"
timeout = 30
keep_alive = true
reconnect_on_failure = true
max_reconnect_attempts = 10
health_check_enabled = false
mode = "proxy"
auto_start = true
command = ["{{PYTHON_EXECUTABLE}}", "-m", "environments_mcp_server", "start", "--transport", "streamable-http", "--port", "4041"]
startup_delay = 3


# ============================================================================
# Tool Manager Configuration
Expand All @@ -78,13 +85,4 @@ version_suffix_format = "_v{version}"
# REST API Configuration
# ============================================================================
[api]
enabled = true
path_prefix = "/api/v1"
host = "0.0.0.0"
port = 8888
cors_enabled = true
cors_origins = ["http://localhost:3000"]
cors_methods = ["GET", "POST", "PUT", "DELETE"]
docs_enabled = true
docs_path = "/docs"
openapi_path = "/openapi.json"
enabled = false
22 changes: 11 additions & 11 deletions src/anaconda_mcp/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,37 @@


def _render_config_template(config_path: str) -> str:
"""Render config template by replacing {{PYTHON_EXECUTABLE}} with sys.executable.
"""Render config template by replacing placeholders with runtime values.

Creates a temporary rendered config file from the template.
Supports multiple ways to specify the Python executable:
1. Environment variable: ANACONDA_MCP_PYTHON_EXECUTABLE
2. sys.executable (default)
Placeholders:
{{PYTHON_EXECUTABLE}} - resolved from ANACONDA_MCP_PYTHON_EXECUTABLE or sys.executable
{{ANACONDA_TOKEN}} - resolved from anaconda-auth token (empty string if not authenticated)

Returns the path to the rendered config file.
"""
from anaconda_mcp.auth import get_auth_token

config_file = Path(config_path)

template_path = config_file.parent / f"{config_file.name}.template"

# If template exists, use it; otherwise use the config file itself
source_path = template_path if template_path.exists() else config_file

if not source_path.exists():
return config_path

content = source_path.read_text()

# Determine which Python executable to use
# Priority: 1. Environment variable, 2. sys.executable
python_executable = settings.PYTHON_EXECUTABLE or sys.executable

# Replace the placeholder with the Python executable
# Escape backslashes for Windows paths
python_path = python_executable.replace("\\", "\\\\")
content = content.replace("{{PYTHON_EXECUTABLE}}", python_path)
content = content.replace('"python"', f'"{python_path}"') # Fallback for non-template
content = content.replace('"python"', f'"{python_path}"')

anaconda_token = get_auth_token()
if anaconda_token is None:
raise RuntimeError("Not authenticated with Anaconda. Run 'anaconda-auth login' or sign in when prompted.")
content = content.replace("{{ANACONDA_TOKEN}}", anaconda_token)

rendered_fd, rendered_path = tempfile.mkstemp(suffix=".toml", prefix="mcp_compose_")
try:
Expand Down
Loading