arcade-mcp/pyproject.toml
Eric Gustin 5602578b2f
Worker Stability (#688)
This PR does three things:
1. Executes synchronous tool calls in thread pool allowing for up to 4 +
# of CPUs executions in parallel.
2. Makes force quitting via double SIGINT/SIGTERM possible and via
single SIGINT/SIGTERM + graceful shutdown timeout expiry possible, even
if there are active connections.
3. Sets `timeout_graceful_shutdown` to
`ARCADE_UVICORN_TIMEOUT_GRACEFUL_SHUTDOWN` env var if set, else defaults
to 15.
4. Disable the worker health check span to reduce noise

Tradeoffs:
Since this PR introduces executing synchronous tools via `await
asyncio.to_thread(func, **func_args)`, this means that there is no way
for the thread to be killed until it finishes. The ramifications of this
is that the force quitting logic that is also implemented in this PR has
to be very harsh `os._exit(1)` just in case there is a sync tool
actively executing. This means that `MCPApp` teardown logic will not
execute when force quitting is required. Although this was already the
case because we weren't previously able to force quit! This tradeoff is
justified for now since "parallel" tool executions will relieve us of
many worker timeouts that we are seeing in prod.

Future work:
Minimize/eliminate the need for `os._exit(1)` such that `MCPApp`
teardown logic will always execute, even when force quitting. The
solution will likely be moving away from `await asyncio.to_thread(func,
**func_args)` (while maintaining "parallelism" and then utilize the
`TaskTrackerMiddleware` introduced in this PR to cancel all of the
active HTTP requests.

Resolves PLT-713
2025-11-20 11:13:41 -08:00

161 lines
3.5 KiB
TOML

[project]
name = "arcade-mcp"
version = "1.5.3"
description = "Arcade.dev - Tool Calling platform for Agents"
readme = "README.md"
license = {file = "LICENSE"}
authors = [
{name = "Arcade", email = "dev@arcade.dev"},
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
requires-python = ">=3.10"
dependencies = [
# CLI dependencies
"arcade-mcp-server>=1.7.2,<2.0.0",
"arcade-core>=3.3.3,<4.0.0",
"typer==0.10.0",
"rich==13.9.4",
"Jinja2==3.1.6",
"arcadepy==1.8.0",
"tqdm==4.67.1",
"openai==1.82.1",
"click==8.1.8",
"posthog==6.7.6",
]
[project.optional-dependencies]
all = [
# evals
"scipy>=1.14.0",
"numpy>=2.0.0",
"scikit-learn>=1.5.0",
"pytz>=2024.1",
"python-dateutil>=2.8.2",
# mcp
"arcade-mcp-server>=1.7.2,<2.0.0",
# serve
"arcade-serve>=3.0.0,<4.0.0",
# tdk
"arcade-tdk>=3.0.0,<4.0.0",
]
# Evals also depends on arcade-core and openai, but they are already required deps
evals = [
"scipy>=1.14.0",
"numpy>=2.0.0",
"scikit-learn>=1.5.0",
"pytz>=2024.1",
"python-dateutil>=2.8.2",
]
[tool.uv]
dev-dependencies = [
"pytest>=8.1.2",
"pytest-cov>=4.0.0",
"pytest-asyncio>=0.23.7",
"mypy>=1.5.1",
"pre-commit>=3.4.0",
"ruff>=0.4.0",
"types-PyYAML>=6.0.0",
"types-python-dateutil>=2.8.2",
"types-pytz>=2024.1",
]
# CLI entry point
[project.scripts]
arcade = "arcade_cli.main:cli"
arcade-mcp = "arcade_cli.main:cli"
[tool.uv.sources]
# Workspace member sources
arcade-core = { workspace = true }
arcade-tdk = { workspace = true }
arcade-serve = { workspace = true }
arcade-mcp-server = { workspace = true }
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = [
"libs/arcade-cli/arcade_cli",
"libs/arcade-evals/arcade_evals",
]
[tool.uv.workspace]
members = [
"libs/arcade-core",
"libs/arcade-tdk",
"libs/arcade-serve",
"libs/arcade-mcp-server",
]
[tool.mypy]
python_version = "3.10"
disallow_untyped_defs = true
disallow_any_unimported = true
no_implicit_optional = true
check_untyped_defs = true
warn_return_any = true
warn_unused_ignores = true
show_error_codes = true
ignore_missing_imports = true
exclude = [
'.*{{.*}}.*' # Ignore files that have names that use Jinja template syntax
]
[tool.pytest.ini_options]
testpaths = ["libs/tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--strict-config",
"--verbose",
"--cov=libs",
"--cov-report=term-missing",
"--cov-report=html",
"--cov-report=xml",
]
[tool.coverage.run]
source = ["libs"]
omit = [
"*/tests/*",
"*/test_*",
"*/__pycache__/*",
]
parallel = true
patch = ["subprocess"]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]
[tool.ruff]
target-version = "py310"
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "RUF"]
ignore = ["E501", "S105"]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]