Versions: * arcade-mcp\==1.0.0rc1 * arcade-mcp-server\==1.0.0rc1 * arcade-core\==2.5.0rc1 * arcade-tdk\==2.6.0rc1 * arcade-serve\==2.2.0rc1 ### Summary Adds first-class MCP support across Arcade, introduces a new MCP server and CLI, unifies the project under the arcade-mcp name, overhauls templates/scaffolding, and improves developer tooling, secrets management, and examples. ### Highlights - **MCP Server & Core** - New MCP server with stdio and HTTP/SSE transports, session management, resumability, and lifecycle handling. - FastAPI-like `MCPApp` for building servers with lazy init; integrated worker+MCP HTTP app option. - Middleware system (logging and error handling), robust exception hierarchy, and Pydantic-based settings. - Async-safe managers for tools, resources, and prompts backed by registries and locks. - Developer-facing, transport-agnostic runtime context interfaces (logs, tools, prompts, resources, sampling, UI, notifications). - Conversion from Arcade ToolDefinition to MCP tool schema; OpenAI JSON tool schema converter. - Parser supports `@app.tool`/`@app.tool(...)` decorators. - **CLI** - New `mcp` command to run MCP servers with stdio or HTTP/SSE. - New `secret` command to set/list/unset tool secrets (supports .env input, preserves original casing for lookups). - `new` command refactored; option to create a full toolkit package with scaffolding. - `chat` command removed. - `serve.py` imports updated to `arcade_serve.fastapi.telemetry`; version retrieval now uses `arcade-mcp`. - `show.py` refactor to use new local catalog utilities. - `display_tool_details` improved: adds “Default” column and handles nested properties. - **Configuration & Discovery** - New `configure.py` to set up Claude Desktop, Cursor, and VS Code to connect to local or Arcade Cloud MCP servers. - Discovery utilities to find/install toolkits, build `ToolCatalog`s, analyze files for tools, load kits from directories (pyproject parsing), and build minimal toolkits. - Better handling of provider API key resolution and evaluation suite loading. - **Templates & Scaffolding** - Reorganized template structure (minimal vs full); moved `.pre-commit-config.yaml`, `.ruff.toml`, license, Makefile, README, tests, and tools layout to correct paths. - Minimal template adds `.env.example` for runtime secret injection. - Template pyproject updated for MCP servers; includes sample server with greeting and secret-reveal tools. - Authorization flow in templates simplified. - **Repo-wide Renaming & Examples** - Migrates references from `arcade-ai` to `arcade-mcp` across READMEs, scripts, and package metadata. - Examples updated (LangChain/LangGraph/AI SDK/TypeScript) and package name changed to `arcade-mcp-sdk`. - **Evals & Core Utilities** - Evals now use OpenAI tooling format (`OpenAIToolList`, `to_openai`); `tool_eval` takes `provider_api_key`. - Core utilities: fixed `does_function_return_value` by dedenting before parse; version bump to `2.5.0rc1` and dependency cleanup. - **Tooling & CI** - `setup-uv-env` action splits toolkit vs contrib dependency installation. - Pre-commit: excludes `libs/arcade-mcp-server/mkdocs.yml` and `libs/tests/` from YAML and Ruff hooks; Ruff per-file ignores (e.g., C901 in `libs/**/*.py`, TRY400 in server docs paths). - Makefile updates for uv env setup, quality checks, tests, builds, and new `shell` target. - Added Makefile to MCP server library to streamline dev workflow. - **Cleanup** - Removed `claude.json` config. - Simplified stdio entrypoint; removed unused imports (`arcade_gmail`, `arcade_search`). ### Breaking Changes - **CLI**: `chat` command removed; use `mcp`, `secret`, and updated `new`. - **Naming**: All users should update references from `arcade-ai` to `arcade-mcp`. - **Templates**: File paths moved; downstream scripts referencing old template locations may need updates. ### Getting Started - Run an MCP server: - `arcade mcp --stdio --toolkits your_toolkit` - `arcade mcp --http --toolkits your_toolkit` - Manage secrets: - `arcade secret set your_toolkit KEY=value` - `arcade secret list your_toolkit` - `arcade secret unset your_toolkit KEY` - Configure clients: - `arcade configure` to set up Claude Desktop, Cursor, and VS Code for local/Arcade Cloud MCP. --------- Co-authored-by: Sam Partee <sam@arcade-ai.com> Co-authored-by: Shub <125150494+shubcodes@users.noreply.github.com>
252 lines
6.6 KiB
Python
252 lines
6.6 KiB
Python
"""
|
|
MCP Settings Management
|
|
|
|
Provides Pydantic-based settings with validation and environment variable support.
|
|
"""
|
|
|
|
import os
|
|
from typing import Any
|
|
|
|
from pydantic import Field, field_validator
|
|
from pydantic_settings import BaseSettings
|
|
|
|
|
|
class NotificationSettings(BaseSettings):
|
|
"""Notification-related settings."""
|
|
|
|
rate_limit_per_minute: int = Field(
|
|
default=60,
|
|
description="Maximum notifications per minute per client",
|
|
ge=1,
|
|
le=1000,
|
|
)
|
|
default_debounce_ms: int = Field(
|
|
default=100,
|
|
description="Default debounce time in milliseconds",
|
|
ge=0,
|
|
le=10000,
|
|
)
|
|
max_queued_notifications: int = Field(
|
|
default=1000,
|
|
description="Maximum queued notifications per client",
|
|
ge=10,
|
|
le=10000,
|
|
)
|
|
|
|
model_config = {"env_prefix": "MCP_NOTIFICATION_"}
|
|
|
|
|
|
class TransportSettings(BaseSettings):
|
|
"""Transport-related settings."""
|
|
|
|
session_timeout_seconds: int = Field(
|
|
default=300,
|
|
description="Session timeout in seconds",
|
|
ge=30,
|
|
le=3600,
|
|
)
|
|
cleanup_interval_seconds: int = Field(
|
|
default=10,
|
|
description="Cleanup interval in seconds",
|
|
ge=1,
|
|
le=60,
|
|
)
|
|
max_sessions: int = Field(
|
|
default=1000,
|
|
description="Maximum concurrent sessions",
|
|
ge=1,
|
|
le=10000,
|
|
)
|
|
max_queue_size: int = Field(
|
|
default=1000,
|
|
description="Maximum queue size per session",
|
|
ge=10,
|
|
le=10000,
|
|
)
|
|
|
|
model_config = {"env_prefix": "MCP_TRANSPORT_"}
|
|
|
|
|
|
class ServerSettings(BaseSettings):
|
|
"""Server-related settings."""
|
|
|
|
name: str = Field(
|
|
default="ArcadeMCP",
|
|
description="Server name",
|
|
)
|
|
version: str = Field(
|
|
default="0.1.0dev",
|
|
description="Server version",
|
|
)
|
|
title: str | None = Field(
|
|
default="Arcade MCP",
|
|
description="Server title for display",
|
|
)
|
|
instructions: str | None = Field(
|
|
default=(
|
|
"ArcadeMCP provides access to a wide range of tools and toolkits."
|
|
"Use 'tools/list' to see available tools and 'tools/call' to execute them."
|
|
),
|
|
description="Server instructions for clients",
|
|
)
|
|
|
|
model_config = {"env_prefix": "MCP_SERVER_"}
|
|
|
|
|
|
class MiddlewareSettings(BaseSettings):
|
|
"""Middleware-related settings."""
|
|
|
|
enable_logging: bool = Field(
|
|
default=True,
|
|
description="Enable logging middleware",
|
|
)
|
|
log_level: str = Field(
|
|
default="INFO",
|
|
description="Log level",
|
|
)
|
|
enable_error_handling: bool = Field(
|
|
default=True,
|
|
description="Enable error handling middleware",
|
|
)
|
|
mask_error_details: bool = Field(
|
|
default=False,
|
|
description="Mask error details in production",
|
|
)
|
|
|
|
@field_validator("log_level")
|
|
@classmethod
|
|
def validate_log_level(cls, v: str) -> str:
|
|
"""Validate log level."""
|
|
valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
v = v.upper()
|
|
if v not in valid_levels:
|
|
raise ValueError(f"Invalid log level: {v}. Must be one of {valid_levels}")
|
|
return v
|
|
|
|
model_config = {"env_prefix": "MCP_MIDDLEWARE_"}
|
|
|
|
|
|
class ArcadeSettings(BaseSettings):
|
|
"""Arcade-specific settings."""
|
|
|
|
api_key: str | None = Field(
|
|
default=None,
|
|
description="Arcade API key",
|
|
)
|
|
api_url: str = Field(
|
|
default="https://api.arcade.dev",
|
|
description="Arcade API URL",
|
|
)
|
|
auth_disabled: bool = Field(
|
|
default=False,
|
|
description="Disable authentication",
|
|
)
|
|
server_secret: str | None = Field(
|
|
default="dev",
|
|
description="Server secret",
|
|
validation_alias="ARCADE_WORKER_SECRET",
|
|
)
|
|
environment: str = Field(
|
|
default="dev",
|
|
description="Environment (dev or prod.)",
|
|
)
|
|
user_id: str | None = Field(
|
|
default=None,
|
|
description="User ID for Arcade environment",
|
|
)
|
|
|
|
model_config = {"env_prefix": "ARCADE_"}
|
|
|
|
|
|
class ToolEnvironmentSettings(BaseSettings):
|
|
"""Tool environment settings.
|
|
|
|
Every environment variable that is not prefixed
|
|
with one of the prefixes for the other settings
|
|
will be added to the tool environment as an
|
|
available tool secret in the ToolContext
|
|
"""
|
|
|
|
tool_environment: dict[str, Any] = Field(
|
|
default_factory=dict,
|
|
description="Tool environment",
|
|
)
|
|
|
|
def model_post_init(self, __context: Any) -> None:
|
|
"""Populate tool_environment from process env if not provided."""
|
|
if not self.tool_environment:
|
|
excluded_prefixes = ("MCP_", "_")
|
|
self.tool_environment = {
|
|
key: value
|
|
for key, value in os.environ.items()
|
|
if not any(key.startswith(prefix) for prefix in excluded_prefixes)
|
|
}
|
|
|
|
model_config = {
|
|
"env_prefix": "",
|
|
"env_file": ".env",
|
|
"env_file_encoding": "utf-8",
|
|
"case_sensitive": False,
|
|
"extra": "allow",
|
|
}
|
|
|
|
|
|
class MCPSettings(BaseSettings):
|
|
"""Main MCP settings container."""
|
|
|
|
# Sub-settings
|
|
notification: NotificationSettings = Field(
|
|
default_factory=NotificationSettings,
|
|
description="Notification settings",
|
|
)
|
|
transport: TransportSettings = Field(
|
|
default_factory=TransportSettings,
|
|
description="Transport settings",
|
|
)
|
|
server: ServerSettings = Field(
|
|
default_factory=ServerSettings,
|
|
description="Server settings",
|
|
)
|
|
middleware: MiddlewareSettings = Field(
|
|
default_factory=MiddlewareSettings,
|
|
description="Middleware settings",
|
|
)
|
|
arcade: ArcadeSettings = Field(
|
|
default_factory=ArcadeSettings,
|
|
description="Arcade integration settings",
|
|
)
|
|
tool_environment: ToolEnvironmentSettings = Field(
|
|
default_factory=ToolEnvironmentSettings,
|
|
description="Tool environment settings",
|
|
)
|
|
|
|
# Global settings
|
|
debug: bool = Field(
|
|
default=False,
|
|
description="Enable debug mode",
|
|
)
|
|
|
|
model_config = {
|
|
"env_prefix": "MCP_",
|
|
"env_file": ".env",
|
|
"env_file_encoding": "utf-8",
|
|
"case_sensitive": False,
|
|
"extra": "allow",
|
|
}
|
|
|
|
@classmethod
|
|
def from_env(cls) -> "MCPSettings":
|
|
"""Create settings from environment variables."""
|
|
return cls()
|
|
|
|
def tool_secrets(self) -> dict[str, Any]:
|
|
"""Get tool secrets."""
|
|
return self.tool_environment.tool_environment
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Convert settings to dictionary."""
|
|
return self.model_dump(exclude_unset=True)
|
|
|
|
|
|
# Global settings instance
|
|
settings = MCPSettings.from_env()
|