From c50699d5e643c54b128e5a78d1ce7bab1bfbe344 Mon Sep 17 00:00:00 2001 From: Eric Gustin <34000337+EricGustin@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:29:18 -0800 Subject: [PATCH] Migrate OSS toolkits to MCPApp (#782) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > [!NOTE] > **Medium Risk** > Touches multiple toolkits’ runtime entrypoints and context/error/auth plumbing, so breakage risk is mainly around invocation/packaging and tool execution wiring rather than business logic. > > **Overview** > Migrates the BrightData, ClickHouse, LinkedIn, Math, MongoDB, Postgres, and Zendesk OSS toolkits from `arcade-tdk` to `arcade-mcp-server` APIs by updating tool decorators, `Context` types, auth classes, and exception imports. > > Adds per-toolkit `__main__.py` files that construct an `MCPApp`, register module tools, and run via configurable transport/host/port; corresponding `pyproject.toml` updates bump versions, drop `arcade-tdk`/`arcade-serve` deps, and add `project.scripts` console entrypoints. > > Updates tests and eval suites to use `arcade_mcp_server.Context` (mocked) and switches eval `ToolCatalog` imports to `arcade_core`. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9b3e31acb4b35e1d72efd47e2d279c5b19e3ecb0. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../brightdata/arcade_brightdata/__main__.py | 28 ++++++++++++++++++ .../tools/bright_data_tools.py | 10 +++---- toolkits/brightdata/pyproject.toml | 10 +++---- toolkits/brightdata/tests/test_brightdata.py | 16 ++++++---- .../clickhouse/arcade_clickhouse/__main__.py | 29 +++++++++++++++++++ .../arcade_clickhouse/database_engine.py | 2 +- .../arcade_clickhouse/tools/clickhouse.py | 14 ++++----- toolkits/clickhouse/pyproject.toml | 12 ++++---- toolkits/clickhouse/tests/test_clickhouse.py | 15 ++++------ toolkits/linkedin/arcade_linkedin/__main__.py | 28 ++++++++++++++++++ .../linkedin/arcade_linkedin/tools/share.py | 8 ++--- .../linkedin/arcade_linkedin/tools/utils.py | 6 ++-- toolkits/linkedin/conftest.py | 16 ++++++---- toolkits/linkedin/evals/eval_linkedin.py | 2 +- toolkits/linkedin/pyproject.toml | 10 +++---- toolkits/linkedin/tests/test_share.py | 2 +- toolkits/math/arcade_math/__main__.py | 29 +++++++++++++++++++ toolkits/math/arcade_math/tools/arithmetic.py | 2 +- toolkits/math/arcade_math/tools/exponents.py | 2 +- .../math/arcade_math/tools/miscellaneous.py | 2 +- toolkits/math/arcade_math/tools/random.py | 2 +- toolkits/math/arcade_math/tools/rational.py | 2 +- toolkits/math/arcade_math/tools/rounding.py | 2 +- toolkits/math/arcade_math/tools/statistics.py | 2 +- .../math/arcade_math/tools/trigonometry.py | 2 +- toolkits/math/evals/eval_math_tools.py | 2 +- toolkits/math/pyproject.toml | 10 +++---- toolkits/math/tests/test_arithmetic.py | 2 +- toolkits/math/tests/test_exponents.py | 2 +- toolkits/math/tests/test_miscellaneous.py | 2 +- toolkits/math/tests/test_rational.py | 2 +- toolkits/mongodb/arcade_mongodb/__main__.py | 29 +++++++++++++++++++ .../mongodb/arcade_mongodb/database_engine.py | 2 +- .../mongodb/arcade_mongodb/tools/mongodb.py | 16 +++++----- .../mongodb/arcade_mongodb/tools/utils.py | 2 +- toolkits/mongodb/evals/eval_mongodb.py | 2 +- toolkits/mongodb/pyproject.toml | 12 ++++---- .../mongodb/tests/test_json_validation.py | 13 ++++----- toolkits/mongodb/tests/test_mongodb.py | 12 ++++---- .../mongodb/tests/test_write_validation.py | 13 ++++----- toolkits/postgres/arcade_postgres/__main__.py | 29 +++++++++++++++++++ .../arcade_postgres/database_engine.py | 2 +- .../arcade_postgres/tools/postgres.py | 12 ++++---- toolkits/postgres/evals/eval_postgres.py | 2 +- toolkits/postgres/pyproject.toml | 12 ++++---- toolkits/postgres/tests/test_postgres.py | 15 ++++------ toolkits/zendesk/arcade_zendesk/__main__.py | 28 ++++++++++++++++++ .../arcade_zendesk/tools/search_articles.py | 8 ++--- .../arcade_zendesk/tools/system_context.py | 6 ++-- .../zendesk/arcade_zendesk/tools/tickets.py | 14 ++++----- toolkits/zendesk/arcade_zendesk/utils.py | 6 ++-- .../zendesk/arcade_zendesk/who_am_i_util.py | 10 +++---- toolkits/zendesk/evals/eval_articles.py | 2 +- toolkits/zendesk/evals/eval_tickets.py | 2 +- toolkits/zendesk/pyproject.toml | 9 +++--- toolkits/zendesk/tests/conftest.py | 4 +-- .../zendesk/tests/test_search_articles.py | 2 +- 57 files changed, 361 insertions(+), 176 deletions(-) create mode 100644 toolkits/brightdata/arcade_brightdata/__main__.py create mode 100644 toolkits/clickhouse/arcade_clickhouse/__main__.py create mode 100644 toolkits/linkedin/arcade_linkedin/__main__.py create mode 100644 toolkits/math/arcade_math/__main__.py create mode 100644 toolkits/mongodb/arcade_mongodb/__main__.py create mode 100644 toolkits/postgres/arcade_postgres/__main__.py create mode 100644 toolkits/zendesk/arcade_zendesk/__main__.py diff --git a/toolkits/brightdata/arcade_brightdata/__main__.py b/toolkits/brightdata/arcade_brightdata/__main__.py new file mode 100644 index 00000000..7c433a07 --- /dev/null +++ b/toolkits/brightdata/arcade_brightdata/__main__.py @@ -0,0 +1,28 @@ +import sys +from typing import cast + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.mcp_app import TransportType + +import arcade_brightdata + +app = MCPApp( + name="BrightData", + instructions=( + "Use this server when you need to interact with Bright Data to help users " + "scrape web pages, search the web, and extract structured data from websites." + ), +) + +app.add_tools_from_module(arcade_brightdata) + + +def main() -> None: + transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" + host = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1" + port = int(sys.argv[3]) if len(sys.argv) > 3 else 8000 + app.run(transport=cast(TransportType, transport), host=host, port=port) + + +if __name__ == "__main__": + main() diff --git a/toolkits/brightdata/arcade_brightdata/tools/bright_data_tools.py b/toolkits/brightdata/arcade_brightdata/tools/bright_data_tools.py index ba6abace..3d51739c 100644 --- a/toolkits/brightdata/arcade_brightdata/tools/bright_data_tools.py +++ b/toolkits/brightdata/arcade_brightdata/tools/bright_data_tools.py @@ -4,7 +4,8 @@ from enum import Enum from typing import Annotated, Any, cast import requests -from arcade_core.errors import RetryableToolError +from arcade_mcp_server import Context, tool +from arcade_mcp_server.exceptions import RetryableToolError from arcade_mcp_server.metadata import ( Behavior, Classification, @@ -12,7 +13,6 @@ from arcade_mcp_server.metadata import ( ServiceDomain, ToolMetadata, ) -from arcade_tdk import ToolContext, tool from arcade_brightdata.bright_data_client import BrightDataClient @@ -74,7 +74,7 @@ class SourceType(str, Enum): ), ) def scrape_as_markdown( - context: ToolContext, + context: Context, url: Annotated[str, "URL to scrape"], ) -> Annotated[str, "Scraped webpage content as Markdown"]: """ @@ -108,7 +108,7 @@ def scrape_as_markdown( ), ) def search_engine( # noqa: C901 - context: ToolContext, + context: Context, query: Annotated[str, "Search query"], engine: Annotated[SearchEngine, "Search engine to use"] = SearchEngine.GOOGLE, language: Annotated[str | None, "Two-letter language code"] = None, @@ -218,7 +218,7 @@ def search_engine( # noqa: C901 ), ) def web_data_feed( - context: ToolContext, + context: Context, source_type: Annotated[SourceType, "Type of data source"], url: Annotated[str, "URL of the web resource to extract data from"], num_of_reviews: Annotated[ diff --git a/toolkits/brightdata/pyproject.toml b/toolkits/brightdata/pyproject.toml index a2341e46..73aaf356 100644 --- a/toolkits/brightdata/pyproject.toml +++ b/toolkits/brightdata/pyproject.toml @@ -4,11 +4,10 @@ build-backend = "hatchling.build" [project] name = "arcade_brightdata" -version = "0.3.0" +version = "0.4.0" description = "Search, Crawl and Scrape any site, at scale, without getting blocked" requires-python = ">=3.10" dependencies = [ - "arcade-tdk>=3.0.0,<4.0.0", "arcade-mcp-server>=1.17.0,<2.0.0", "requests>=2.32.5", ] @@ -16,10 +15,13 @@ dependencies = [ name = "meirk-brd" email = "meirk@brightdata.com" +[project.scripts] +arcade-brightdata = "arcade_brightdata.__main__:main" +arcade_brightdata = "arcade_brightdata.__main__:main" + [project.optional-dependencies] dev = [ "arcade-mcp[all]>=1.2.0,<2.0.0", - "arcade-serve>=3.0.0,<4.0.0", "pytest>=8.3.0,<8.4.0", "pytest-cov>=4.0.0,<4.1.0", "pytest-mock>=3.11.1,<3.12.0", @@ -48,8 +50,6 @@ ignore_missing_imports = "True" [tool.uv.sources] arcade-mcp = { path = "../../", editable = true } -arcade-serve = { path = "../../libs/arcade-serve/", editable = true } -arcade-tdk = { path = "../../libs/arcade-tdk/", editable = true } arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true } [tool.pytest.ini_options] diff --git a/toolkits/brightdata/tests/test_brightdata.py b/toolkits/brightdata/tests/test_brightdata.py index f605dc2c..98555af0 100644 --- a/toolkits/brightdata/tests/test_brightdata.py +++ b/toolkits/brightdata/tests/test_brightdata.py @@ -1,10 +1,11 @@ from os import environ +from unittest.mock import MagicMock as _MagicMock from unittest.mock import Mock, patch import pytest import requests -from arcade_tdk import ToolContext, ToolSecretItem -from arcade_tdk.errors import ToolExecutionError +from arcade_mcp_server import Context +from arcade_mcp_server.exceptions import ToolExecutionError from arcade_brightdata.bright_data_client import BrightDataClient from arcade_brightdata.tools.bright_data_tools import ( @@ -21,10 +22,13 @@ BRIGHTDATA_ZONE = environ.get("TEST_BRIGHTDATA_ZONE") or "unblocker" @pytest.fixture def mock_context(): - context = ToolContext() - context.secrets = [] - context.secrets.append(ToolSecretItem(key="BRIGHTDATA_API_KEY", value=BRIGHTDATA_API_KEY)) - context.secrets.append(ToolSecretItem(key="BRIGHTDATA_ZONE", value=BRIGHTDATA_ZONE)) + context = _MagicMock(spec=Context) + context.get_secret = _MagicMock( + side_effect=lambda key: { + "BRIGHTDATA_API_KEY": BRIGHTDATA_API_KEY, + "BRIGHTDATA_ZONE": BRIGHTDATA_ZONE, + }[key] + ) return context diff --git a/toolkits/clickhouse/arcade_clickhouse/__main__.py b/toolkits/clickhouse/arcade_clickhouse/__main__.py new file mode 100644 index 00000000..39c445e1 --- /dev/null +++ b/toolkits/clickhouse/arcade_clickhouse/__main__.py @@ -0,0 +1,29 @@ +import sys +from typing import cast + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.mcp_app import TransportType + +import arcade_clickhouse + +app = MCPApp( + name="ClickHouse", + instructions=( + "Use this server when you need to interact with ClickHouse to help users " + "query, explore, and manage their ClickHouse databases." + ), +) + +app.add_tools_from_module(arcade_clickhouse) + + +def main() -> None: + transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" + host = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1" + port = int(sys.argv[3]) if len(sys.argv) > 3 else 8000 + + app.run(transport=cast(TransportType, transport), host=host, port=port) + + +if __name__ == "__main__": + main() diff --git a/toolkits/clickhouse/arcade_clickhouse/database_engine.py b/toolkits/clickhouse/arcade_clickhouse/database_engine.py index d5014c09..75e5a240 100644 --- a/toolkits/clickhouse/arcade_clickhouse/database_engine.py +++ b/toolkits/clickhouse/arcade_clickhouse/database_engine.py @@ -3,7 +3,7 @@ from typing import Any, ClassVar from urllib.parse import urlparse import clickhouse_connect -from arcade_tdk.errors import RetryableToolError +from arcade_mcp_server.exceptions import RetryableToolError MAX_ROWS_RETURNED = 1000 TEST_QUERY = "SELECT 1" diff --git a/toolkits/clickhouse/arcade_clickhouse/tools/clickhouse.py b/toolkits/clickhouse/arcade_clickhouse/tools/clickhouse.py index b1fd2cbe..4c796559 100644 --- a/toolkits/clickhouse/arcade_clickhouse/tools/clickhouse.py +++ b/toolkits/clickhouse/arcade_clickhouse/tools/clickhouse.py @@ -1,8 +1,8 @@ from typing import Annotated, Any +from arcade_mcp_server import Context, tool +from arcade_mcp_server.exceptions import RetryableToolError from arcade_mcp_server.metadata import Behavior, Operation, ToolMetadata -from arcade_tdk import ToolContext, tool -from arcade_tdk.errors import RetryableToolError from ..database_engine import MAX_ROWS_RETURNED, DatabaseEngine @@ -20,7 +20,7 @@ from ..database_engine import MAX_ROWS_RETURNED, DatabaseEngine ), ) async def discover_schemas( - context: ToolContext, + context: Context, ) -> list[str]: """Discover all the schemas in the ClickHouse database. @@ -43,7 +43,7 @@ async def discover_schemas( ), ) async def discover_databases( - context: ToolContext, + context: Context, ) -> list[str]: """Discover all the databases in the ClickHouse database.""" async with await DatabaseEngine.get_engine( @@ -66,7 +66,7 @@ async def discover_databases( ), ) async def discover_tables( - context: ToolContext, + context: Context, ) -> list[str]: """Discover all the tables in the ClickHouse database when the list of tables is not known. @@ -92,7 +92,7 @@ async def discover_tables( ), ) async def get_table_schema( - context: ToolContext, + context: Context, schema_name: Annotated[str, "The schema to get the table schema of"], table_name: Annotated[str, "The table to get the schema of"], ) -> list[str]: @@ -120,7 +120,7 @@ async def get_table_schema( ), ) async def execute_select_query( - context: ToolContext, + context: Context, select_clause: Annotated[ str, "This is the part of the SQL query that comes after the SELECT keyword wish a comma separated list of columns you wish to return. Do not include the SELECT keyword.", diff --git a/toolkits/clickhouse/pyproject.toml b/toolkits/clickhouse/pyproject.toml index 009d3511..8442433a 100644 --- a/toolkits/clickhouse/pyproject.toml +++ b/toolkits/clickhouse/pyproject.toml @@ -4,11 +4,10 @@ build-backend = "hatchling.build" [project] name = "arcade_clickhouse" -version = "0.2.0" +version = "0.3.0" description = "Tools to query and explore a ClickHouse database" requires-python = ">=3.10" dependencies = [ - "arcade-tdk>=3.0.0,<4.0.0", "arcade-mcp-server>=1.17.0,<2.0.0", "clickhouse-connect>=0.7.0", "pydantic>=2.11.7", @@ -22,11 +21,9 @@ dependencies = [ name = "evantahler" email = "support@arcade.dev" - [project.optional-dependencies] dev = [ "arcade-mcp[all]>=1.2.0,<2.0.0", - "arcade-serve>=3.0.0,<4.0.0", "pytest>=8.3.0,<8.4.0", "pytest-cov>=4.0.0,<4.1.0", "pytest-mock>=3.11.1,<3.12.0", @@ -37,14 +34,15 @@ dev = [ "ruff>=0.7.4,<0.8.0", ] +[project.scripts] +arcade-clickhouse = "arcade_clickhouse.__main__:main" +arcade_clickhouse = "arcade_clickhouse.__main__:main" + # Use local path sources for arcade libs when working locally [tool.uv.sources] arcade-mcp = { path = "../../", editable = true } -arcade-serve = { path = "../../libs/arcade-serve/", editable = true } -arcade-tdk = { path = "../../libs/arcade-tdk/", editable = true } arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true } - [tool.mypy] files = [ "arcade_clickhouse/**/*.py",] python_version = "3.10" diff --git a/toolkits/clickhouse/tests/test_clickhouse.py b/toolkits/clickhouse/tests/test_clickhouse.py index 2f4875e7..bad0a42d 100644 --- a/toolkits/clickhouse/tests/test_clickhouse.py +++ b/toolkits/clickhouse/tests/test_clickhouse.py @@ -1,5 +1,6 @@ import os from os import environ +from unittest.mock import MagicMock import pytest import pytest_asyncio @@ -10,8 +11,8 @@ from arcade_clickhouse.tools.clickhouse import ( execute_select_query, get_table_schema, ) -from arcade_tdk import ToolContext, ToolSecretItem -from arcade_tdk.errors import RetryableToolError +from arcade_mcp_server import Context +from arcade_mcp_server.exceptions import RetryableToolError CLICKHOUSE_DATABASE_CONNECTION_STRING = ( environ.get("TEST_CLICKHOUSE_DATABASE_CONNECTION_STRING") @@ -21,14 +22,8 @@ CLICKHOUSE_DATABASE_CONNECTION_STRING = ( @pytest.fixture def mock_context(): - context = ToolContext() - context.secrets = [] - context.secrets.append( - ToolSecretItem( - key="CLICKHOUSE_DATABASE_CONNECTION_STRING", value=CLICKHOUSE_DATABASE_CONNECTION_STRING - ) - ) - + context = MagicMock(spec=Context) + context.get_secret = MagicMock(return_value=CLICKHOUSE_DATABASE_CONNECTION_STRING) return context diff --git a/toolkits/linkedin/arcade_linkedin/__main__.py b/toolkits/linkedin/arcade_linkedin/__main__.py new file mode 100644 index 00000000..01f392d0 --- /dev/null +++ b/toolkits/linkedin/arcade_linkedin/__main__.py @@ -0,0 +1,28 @@ +import sys +from typing import cast + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.mcp_app import TransportType + +import arcade_linkedin + +app = MCPApp( + name="LinkedIn", + instructions=( + "Use this server when you need to interact with LinkedIn to help users " + "create and share posts on their LinkedIn profile." + ), +) + +app.add_tools_from_module(arcade_linkedin) + + +def main() -> None: + transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" + host = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1" + port = int(sys.argv[3]) if len(sys.argv) > 3 else 8000 + app.run(transport=cast(TransportType, transport), host=host, port=port) + + +if __name__ == "__main__": + main() diff --git a/toolkits/linkedin/arcade_linkedin/tools/share.py b/toolkits/linkedin/arcade_linkedin/tools/share.py index b804f84e..53a6dbaa 100644 --- a/toolkits/linkedin/arcade_linkedin/tools/share.py +++ b/toolkits/linkedin/arcade_linkedin/tools/share.py @@ -1,5 +1,8 @@ from typing import Annotated +from arcade_mcp_server import Context, tool +from arcade_mcp_server.auth import LinkedIn +from arcade_mcp_server.exceptions import ToolExecutionError from arcade_mcp_server.metadata import ( Behavior, Classification, @@ -7,9 +10,6 @@ from arcade_mcp_server.metadata import ( ServiceDomain, ToolMetadata, ) -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import LinkedIn -from arcade_tdk.errors import ToolExecutionError from arcade_linkedin.tools.utils import _handle_linkedin_api_error, _send_linkedin_request @@ -32,7 +32,7 @@ from arcade_linkedin.tools.utils import _handle_linkedin_api_error, _send_linked ), ) async def create_text_post( - context: ToolContext, + context: Context, text: Annotated[str, "The text content of the post"], ) -> Annotated[str, "URL of the shared post"]: """Share a new text post to LinkedIn.""" diff --git a/toolkits/linkedin/arcade_linkedin/tools/utils.py b/toolkits/linkedin/arcade_linkedin/tools/utils.py index 782b8409..fb457ec2 100644 --- a/toolkits/linkedin/arcade_linkedin/tools/utils.py +++ b/toolkits/linkedin/arcade_linkedin/tools/utils.py @@ -1,12 +1,12 @@ import httpx -from arcade_tdk import ToolContext -from arcade_tdk.errors import ToolExecutionError +from arcade_mcp_server import Context +from arcade_mcp_server.exceptions import ToolExecutionError from arcade_linkedin.tools.constants import LINKEDIN_BASE_URL async def _send_linkedin_request( - context: ToolContext, + context: Context, method: str, endpoint: str, params: dict | None = None, diff --git a/toolkits/linkedin/conftest.py b/toolkits/linkedin/conftest.py index 7cba84e5..59c0562d 100644 --- a/toolkits/linkedin/conftest.py +++ b/toolkits/linkedin/conftest.py @@ -1,14 +1,18 @@ +from unittest.mock import MagicMock + import pytest -from arcade_tdk import ToolAuthorizationContext, ToolContext +from arcade_mcp_server import Context @pytest.fixture def tool_context(): - """Fixture for the ToolContext with mock authorization.""" - return ToolContext( - authorization=ToolAuthorizationContext(token="test_token", user_info={"sub": "test_user"}), # noqa: S106 - user_id="test_user", - ) + """Fixture for the tool Context with mock authorization.""" + context = MagicMock(spec=Context) + authorization = MagicMock() + authorization.token = "test_token" # noqa: S105 + authorization.user_info = {"sub": "test_user"} + context.authorization = authorization + return context @pytest.fixture diff --git a/toolkits/linkedin/evals/eval_linkedin.py b/toolkits/linkedin/evals/eval_linkedin.py index 4c25d50b..9156aa50 100644 --- a/toolkits/linkedin/evals/eval_linkedin.py +++ b/toolkits/linkedin/evals/eval_linkedin.py @@ -1,3 +1,4 @@ +from arcade_core import ToolCatalog from arcade_evals import ( EvalRubric, EvalSuite, @@ -5,7 +6,6 @@ from arcade_evals import ( SimilarityCritic, tool_eval, ) -from arcade_tdk import ToolCatalog import arcade_linkedin from arcade_linkedin.tools.share import create_text_post diff --git a/toolkits/linkedin/pyproject.toml b/toolkits/linkedin/pyproject.toml index 098fd92c..e2c8b937 100644 --- a/toolkits/linkedin/pyproject.toml +++ b/toolkits/linkedin/pyproject.toml @@ -4,11 +4,10 @@ build-backend = "hatchling.build" [project] name = "arcade_linkedin" -version = "0.2.0" +version = "0.3.0" description = "Arcade.dev LLM tools for LinkedIn" requires-python = ">=3.10" dependencies = [ - "arcade-tdk>=3.0.0,<4.0.0", "arcade-mcp-server>=1.17.0,<2.0.0", "httpx>=0.27.2,<1.0.0", ] @@ -16,10 +15,13 @@ dependencies = [ name = "Arcade" email = "dev@arcade.dev" +[project.scripts] +arcade-linkedin = "arcade_linkedin.__main__:main" +arcade_linkedin = "arcade_linkedin.__main__:main" + [project.optional-dependencies] dev = [ "arcade-mcp[all]>=1.2.0,<2.0.0", - "arcade-serve>=3.0.0,<4.0.0", "pytest>=8.3.0,<8.4.0", "pytest-cov>=4.0.0,<4.1.0", "pytest-asyncio>=0.24.0,<0.25.0", @@ -33,8 +35,6 @@ dev = [ # Use local path sources for arcade libs when working locally [tool.uv.sources] arcade-mcp = {path = "../../", editable = true} -arcade-tdk = { path = "../../libs/arcade-tdk/", editable = true } -arcade-serve = { path = "../../libs/arcade-serve/", editable = true } arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true } [tool.mypy] diff --git a/toolkits/linkedin/tests/test_share.py b/toolkits/linkedin/tests/test_share.py index 9aa39882..590f8c97 100644 --- a/toolkits/linkedin/tests/test_share.py +++ b/toolkits/linkedin/tests/test_share.py @@ -1,7 +1,7 @@ from unittest.mock import AsyncMock, MagicMock import pytest -from arcade_tdk.errors import ToolExecutionError +from arcade_mcp_server.exceptions import ToolExecutionError from arcade_linkedin.tools.share import create_text_post diff --git a/toolkits/math/arcade_math/__main__.py b/toolkits/math/arcade_math/__main__.py new file mode 100644 index 00000000..3703f55a --- /dev/null +++ b/toolkits/math/arcade_math/__main__.py @@ -0,0 +1,29 @@ +import sys +from typing import cast + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.mcp_app import TransportType + +import arcade_math + +app = MCPApp( + name="Math", + instructions=( + "Use this server when you need to perform mathematical calculations to help users " + "with arithmetic, trigonometry, statistics, exponents, rounding, and other math operations." + ), +) + +app.add_tools_from_module(arcade_math) + + +def main() -> None: + transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" + host = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1" + port = int(sys.argv[3]) if len(sys.argv) > 3 else 8000 + + app.run(transport=cast(TransportType, transport), host=host, port=port) + + +if __name__ == "__main__": + main() diff --git a/toolkits/math/arcade_math/tools/arithmetic.py b/toolkits/math/arcade_math/tools/arithmetic.py index b0029a1f..2ae5f8f8 100644 --- a/toolkits/math/arcade_math/tools/arithmetic.py +++ b/toolkits/math/arcade_math/tools/arithmetic.py @@ -2,8 +2,8 @@ import decimal from decimal import Decimal from typing import Annotated +from arcade_mcp_server import tool from arcade_mcp_server.metadata import Behavior, ToolMetadata -from arcade_tdk import tool decimal.getcontext().prec = 100 diff --git a/toolkits/math/arcade_math/tools/exponents.py b/toolkits/math/arcade_math/tools/exponents.py index 9192e748..c4658e3d 100644 --- a/toolkits/math/arcade_math/tools/exponents.py +++ b/toolkits/math/arcade_math/tools/exponents.py @@ -3,8 +3,8 @@ import math from decimal import Decimal from typing import Annotated +from arcade_mcp_server import tool from arcade_mcp_server.metadata import Behavior, ToolMetadata -from arcade_tdk import tool decimal.getcontext().prec = 100 diff --git a/toolkits/math/arcade_math/tools/miscellaneous.py b/toolkits/math/arcade_math/tools/miscellaneous.py index cdba5322..85a4315c 100644 --- a/toolkits/math/arcade_math/tools/miscellaneous.py +++ b/toolkits/math/arcade_math/tools/miscellaneous.py @@ -3,8 +3,8 @@ import math from decimal import Decimal from typing import Annotated +from arcade_mcp_server import tool from arcade_mcp_server.metadata import Behavior, ToolMetadata -from arcade_tdk import tool decimal.getcontext().prec = 100 diff --git a/toolkits/math/arcade_math/tools/random.py b/toolkits/math/arcade_math/tools/random.py index d12846c6..304823c8 100644 --- a/toolkits/math/arcade_math/tools/random.py +++ b/toolkits/math/arcade_math/tools/random.py @@ -1,8 +1,8 @@ import random from typing import Annotated +from arcade_mcp_server import tool from arcade_mcp_server.metadata import Behavior, ToolMetadata -from arcade_tdk import tool @tool( diff --git a/toolkits/math/arcade_math/tools/rational.py b/toolkits/math/arcade_math/tools/rational.py index e76f2572..bf9f6039 100644 --- a/toolkits/math/arcade_math/tools/rational.py +++ b/toolkits/math/arcade_math/tools/rational.py @@ -1,8 +1,8 @@ import math from typing import Annotated +from arcade_mcp_server import tool from arcade_mcp_server.metadata import Behavior, ToolMetadata -from arcade_tdk import tool @tool( diff --git a/toolkits/math/arcade_math/tools/rounding.py b/toolkits/math/arcade_math/tools/rounding.py index 1e6e04ba..1630f0b4 100644 --- a/toolkits/math/arcade_math/tools/rounding.py +++ b/toolkits/math/arcade_math/tools/rounding.py @@ -3,8 +3,8 @@ import math from decimal import Decimal from typing import Annotated +from arcade_mcp_server import tool from arcade_mcp_server.metadata import Behavior, ToolMetadata -from arcade_tdk import tool decimal.getcontext().prec = 100 diff --git a/toolkits/math/arcade_math/tools/statistics.py b/toolkits/math/arcade_math/tools/statistics.py index 1bc6c3fd..9e996543 100644 --- a/toolkits/math/arcade_math/tools/statistics.py +++ b/toolkits/math/arcade_math/tools/statistics.py @@ -3,8 +3,8 @@ from decimal import Decimal from statistics import median as stats_median from typing import Annotated +from arcade_mcp_server import tool from arcade_mcp_server.metadata import Behavior, ToolMetadata -from arcade_tdk import tool decimal.getcontext().prec = 100 diff --git a/toolkits/math/arcade_math/tools/trigonometry.py b/toolkits/math/arcade_math/tools/trigonometry.py index a322f824..e31e129f 100644 --- a/toolkits/math/arcade_math/tools/trigonometry.py +++ b/toolkits/math/arcade_math/tools/trigonometry.py @@ -3,8 +3,8 @@ import math from decimal import Decimal from typing import Annotated +from arcade_mcp_server import tool from arcade_mcp_server.metadata import Behavior, ToolMetadata -from arcade_tdk import tool decimal.getcontext().prec = 100 diff --git a/toolkits/math/evals/eval_math_tools.py b/toolkits/math/evals/eval_math_tools.py index c1ff3859..74c3aba6 100644 --- a/toolkits/math/evals/eval_math_tools.py +++ b/toolkits/math/evals/eval_math_tools.py @@ -1,6 +1,7 @@ from collections.abc import Callable from typing import Any +from arcade_core import ToolCatalog from arcade_evals import ( BinaryCritic, EvalRubric, @@ -8,7 +9,6 @@ from arcade_evals import ( ExpectedToolCall, tool_eval, ) -from arcade_tdk import ToolCatalog import arcade_math from arcade_math.tools.arithmetic import ( diff --git a/toolkits/math/pyproject.toml b/toolkits/math/pyproject.toml index 9b03cce1..0253036e 100644 --- a/toolkits/math/pyproject.toml +++ b/toolkits/math/pyproject.toml @@ -4,11 +4,10 @@ build-backend = "hatchling.build" [project] name = "arcade_math" -version = "1.1.0" +version = "1.2.0" description = "Arcade.dev LLM tools for doing math" requires-python = ">=3.10" dependencies = [ - "arcade-tdk>=3.0.0,<4.0.0", "arcade-mcp-server>=1.17.0,<2.0.0", ] [[project.authors]] @@ -18,7 +17,6 @@ email = "dev@arcade.dev" [project.optional-dependencies] dev = [ "arcade-mcp[all]>=1.2.0,<2.0.0", - "arcade-serve>=3.0.0,<4.0.0", "pytest>=8.3.0,<8.4.0", "pytest-cov>=4.0.0,<4.1.0", "pytest-asyncio>=0.24.0,<0.25.0", @@ -29,11 +27,13 @@ dev = [ "ruff>=0.7.4,<0.8.0", ] +[project.scripts] +arcade-math = "arcade_math.__main__:main" +arcade_math = "arcade_math.__main__:main" + # Use local path sources for arcade libs when working locally [tool.uv.sources] arcade-mcp = {path = "../../", editable = true} -arcade-tdk = { path = "../../libs/arcade-tdk/", editable = true } -arcade-serve = { path = "../../libs/arcade-serve/", editable = true } arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true } [tool.mypy] diff --git a/toolkits/math/tests/test_arithmetic.py b/toolkits/math/tests/test_arithmetic.py index 845110ea..a9f7617e 100644 --- a/toolkits/math/tests/test_arithmetic.py +++ b/toolkits/math/tests/test_arithmetic.py @@ -1,5 +1,5 @@ import pytest -from arcade_tdk.errors import ToolExecutionError +from arcade_mcp_server.exceptions import ToolExecutionError from arcade_math.tools.arithmetic import ( add, diff --git a/toolkits/math/tests/test_exponents.py b/toolkits/math/tests/test_exponents.py index be2d6f9e..f7632590 100644 --- a/toolkits/math/tests/test_exponents.py +++ b/toolkits/math/tests/test_exponents.py @@ -1,5 +1,5 @@ import pytest -from arcade_tdk.errors import ToolExecutionError +from arcade_mcp_server.exceptions import ToolExecutionError from arcade_math.tools.exponents import ( log, diff --git a/toolkits/math/tests/test_miscellaneous.py b/toolkits/math/tests/test_miscellaneous.py index 16d7877b..2addab6a 100644 --- a/toolkits/math/tests/test_miscellaneous.py +++ b/toolkits/math/tests/test_miscellaneous.py @@ -1,5 +1,5 @@ import pytest -from arcade_tdk.errors import ToolExecutionError +from arcade_mcp_server.exceptions import ToolExecutionError from arcade_math.tools.miscellaneous import ( abs_val, diff --git a/toolkits/math/tests/test_rational.py b/toolkits/math/tests/test_rational.py index c35bef5c..88d76028 100644 --- a/toolkits/math/tests/test_rational.py +++ b/toolkits/math/tests/test_rational.py @@ -1,5 +1,5 @@ import pytest -from arcade_tdk.errors import ToolExecutionError +from arcade_mcp_server.exceptions import ToolExecutionError from arcade_math.tools.rational import ( gcd, diff --git a/toolkits/mongodb/arcade_mongodb/__main__.py b/toolkits/mongodb/arcade_mongodb/__main__.py new file mode 100644 index 00000000..ece2973b --- /dev/null +++ b/toolkits/mongodb/arcade_mongodb/__main__.py @@ -0,0 +1,29 @@ +import sys +from typing import cast + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.mcp_app import TransportType + +import arcade_mongodb + +app = MCPApp( + name="MongoDB", + instructions=( + "Use this server when you need to interact with MongoDB to help users " + "query, explore, and manage their MongoDB databases and collections." + ), +) + +app.add_tools_from_module(arcade_mongodb) + + +def main() -> None: + transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" + host = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1" + port = int(sys.argv[3]) if len(sys.argv) > 3 else 8000 + + app.run(transport=cast(TransportType, transport), host=host, port=port) + + +if __name__ == "__main__": + main() diff --git a/toolkits/mongodb/arcade_mongodb/database_engine.py b/toolkits/mongodb/arcade_mongodb/database_engine.py index 744890de..7eda9b03 100644 --- a/toolkits/mongodb/arcade_mongodb/database_engine.py +++ b/toolkits/mongodb/arcade_mongodb/database_engine.py @@ -1,6 +1,6 @@ from typing import Any, ClassVar -from arcade_tdk.errors import RetryableToolError +from arcade_mcp_server.exceptions import RetryableToolError from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase from pymongo.errors import ServerSelectionTimeoutError diff --git a/toolkits/mongodb/arcade_mongodb/tools/mongodb.py b/toolkits/mongodb/arcade_mongodb/tools/mongodb.py index 317e09af..8f31841a 100644 --- a/toolkits/mongodb/arcade_mongodb/tools/mongodb.py +++ b/toolkits/mongodb/arcade_mongodb/tools/mongodb.py @@ -1,9 +1,9 @@ import json from typing import Annotated, Any +from arcade_mcp_server import Context, tool +from arcade_mcp_server.exceptions import RetryableToolError from arcade_mcp_server.metadata import Behavior, Operation, ToolMetadata -from arcade_tdk import ToolContext, tool -from arcade_tdk.errors import RetryableToolError from ..database_engine import MAX_RECORDS_RETURNED, DatabaseEngine from .utils import ( @@ -34,7 +34,7 @@ from .utils import ( ), ) async def discover_databases( - context: ToolContext, + context: Context, ) -> list[str]: """Discover all the databases in the MongoDB instance.""" client = await DatabaseEngine.get_instance(context.get_secret("MONGODB_CONNECTION_STRING")) @@ -57,7 +57,7 @@ async def discover_databases( ), ) async def discover_collections( - context: ToolContext, + context: Context, database_name: Annotated[str, "The database name to discover collections in"], ) -> list[str]: """Discover all the collections in the MongoDB database when the list of collections is not known. @@ -84,7 +84,7 @@ async def discover_collections( ), ) async def get_collection_schema( - context: ToolContext, + context: Context, database_name: Annotated[str, "The database name to get the collection schema of"], collection_name: Annotated[str, "The collection to get the schema of"], sample_size: Annotated[ @@ -137,7 +137,7 @@ async def get_collection_schema( ), ) async def find_documents( - context: ToolContext, + context: Context, database_name: Annotated[str, "The database name to query"], collection_name: Annotated[str, "The collection name to query"], filter_dict: Annotated[ @@ -251,7 +251,7 @@ async def find_documents( ), ) async def count_documents( - context: ToolContext, + context: Context, database_name: Annotated[str, "The database name to query"], collection_name: Annotated[str, "The collection name to query"], filter_dict: Annotated[ @@ -299,7 +299,7 @@ async def count_documents( ), ) async def aggregate_documents( - context: ToolContext, + context: Context, database_name: Annotated[str, "The database name to query"], collection_name: Annotated[str, "The collection name to query"], pipeline: Annotated[ diff --git a/toolkits/mongodb/arcade_mongodb/tools/utils.py b/toolkits/mongodb/arcade_mongodb/tools/utils.py index ada7f2a7..d6f074b0 100644 --- a/toolkits/mongodb/arcade_mongodb/tools/utils.py +++ b/toolkits/mongodb/arcade_mongodb/tools/utils.py @@ -2,7 +2,7 @@ import json from datetime import datetime from typing import Any -from arcade_tdk.errors import RetryableToolError +from arcade_mcp_server.exceptions import RetryableToolError from bson import ObjectId diff --git a/toolkits/mongodb/evals/eval_mongodb.py b/toolkits/mongodb/evals/eval_mongodb.py index e1e27511..34aa2049 100644 --- a/toolkits/mongodb/evals/eval_mongodb.py +++ b/toolkits/mongodb/evals/eval_mongodb.py @@ -1,6 +1,7 @@ # RUN ME WITH `uv run arcade evals evals --host api.arcade.dev` import arcade_mongodb +from arcade_core import ToolCatalog from arcade_evals import ( BinaryCritic, EvalRubric, @@ -17,7 +18,6 @@ from arcade_mongodb.tools.mongodb import ( find_documents, get_collection_schema, ) -from arcade_tdk import ToolCatalog # Evaluation rubric rubric = EvalRubric( diff --git a/toolkits/mongodb/pyproject.toml b/toolkits/mongodb/pyproject.toml index 65ba1b77..1fb22831 100644 --- a/toolkits/mongodb/pyproject.toml +++ b/toolkits/mongodb/pyproject.toml @@ -4,11 +4,10 @@ build-backend = "hatchling.build" [project] name = "arcade_mongodb" -version = "0.2.0" +version = "0.3.0" description = "Tools to query and explore a MongoDB database" requires-python = ">=3.10" dependencies = [ - "arcade-tdk>=3.0.0,<4.0.0", "arcade-mcp-server>=1.17.0,<2.0.0", "pymongo>=4.10.1", "pydantic>=2.11.7", @@ -18,11 +17,9 @@ dependencies = [ name = "evantahler" email = "support@arcade.dev" - [project.optional-dependencies] dev = [ "arcade-mcp[all]>=1.2.0,<2.0.0", - "arcade-serve>=3.0.0,<4.0.0", "pytest>=8.3.0,<8.4.0", "pytest-cov>=4.0.0,<4.1.0", "pytest-mock>=3.11.1,<3.12.0", @@ -33,14 +30,15 @@ dev = [ "ruff>=0.7.4,<0.8.0", ] +[project.scripts] +arcade-mongodb = "arcade_mongodb.__main__:main" +arcade_mongodb = "arcade_mongodb.__main__:main" + # Use local path sources for arcade libs when working locally [tool.uv.sources] arcade-mcp = { path = "../../", editable = true } -arcade-serve = { path = "../../libs/arcade-serve/", editable = true } -arcade-tdk = { path = "../../libs/arcade-tdk/", editable = true } arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true } - [tool.mypy] files = [ "arcade_mongodb/**/*.py",] python_version = "3.10" diff --git a/toolkits/mongodb/tests/test_json_validation.py b/toolkits/mongodb/tests/test_json_validation.py index 38c2a476..e99e8c24 100644 --- a/toolkits/mongodb/tests/test_json_validation.py +++ b/toolkits/mongodb/tests/test_json_validation.py @@ -1,19 +1,18 @@ +from unittest.mock import MagicMock + import pytest from arcade_core.errors import ToolExecutionError +from arcade_mcp_server import Context +from arcade_mcp_server.exceptions import RetryableToolError from arcade_mongodb.tools.mongodb import aggregate_documents, count_documents, find_documents -from arcade_tdk import ToolContext, ToolSecretItem -from arcade_tdk.errors import RetryableToolError from .conftest import TEST_MONGODB_CONNECTION_STRING @pytest.fixture def mock_context(): - context = ToolContext() - context.secrets = [] - context.secrets.append( - ToolSecretItem(key="MONGODB_CONNECTION_STRING", value=TEST_MONGODB_CONNECTION_STRING) - ) + context = MagicMock(spec=Context) + context.get_secret = MagicMock(return_value=TEST_MONGODB_CONNECTION_STRING) return context diff --git a/toolkits/mongodb/tests/test_mongodb.py b/toolkits/mongodb/tests/test_mongodb.py index e70c7fa8..14d850a6 100644 --- a/toolkits/mongodb/tests/test_mongodb.py +++ b/toolkits/mongodb/tests/test_mongodb.py @@ -1,6 +1,9 @@ import json +from unittest.mock import MagicMock import pytest +from arcade_mcp_server import Context +from arcade_mcp_server.exceptions import RetryableToolError from arcade_mongodb.database_engine import DatabaseEngine from arcade_mongodb.tools.mongodb import ( # UserStatus, @@ -12,19 +15,14 @@ from arcade_mongodb.tools.mongodb import ( get_collection_schema, # update_user_status, ) -from arcade_tdk import ToolContext, ToolSecretItem -from arcade_tdk.errors import RetryableToolError from .conftest import TEST_MONGODB_CONNECTION_STRING @pytest.fixture def mock_context(): - context = ToolContext() - context.secrets = [] - context.secrets.append( - ToolSecretItem(key="MONGODB_CONNECTION_STRING", value=TEST_MONGODB_CONNECTION_STRING) - ) + context = MagicMock(spec=Context) + context.get_secret = MagicMock(return_value=TEST_MONGODB_CONNECTION_STRING) return context diff --git a/toolkits/mongodb/tests/test_write_validation.py b/toolkits/mongodb/tests/test_write_validation.py index 8830dfd1..f08e4cae 100644 --- a/toolkits/mongodb/tests/test_write_validation.py +++ b/toolkits/mongodb/tests/test_write_validation.py @@ -1,18 +1,17 @@ +from unittest.mock import MagicMock + import pytest +from arcade_mcp_server import Context +from arcade_mcp_server.exceptions import RetryableToolError from arcade_mongodb.tools.mongodb import aggregate_documents, count_documents, find_documents -from arcade_tdk import ToolContext, ToolSecretItem -from arcade_tdk.errors import RetryableToolError from .conftest import TEST_MONGODB_CONNECTION_STRING @pytest.fixture def mock_context(): - context = ToolContext() - context.secrets = [] - context.secrets.append( - ToolSecretItem(key="MONGODB_CONNECTION_STRING", value=TEST_MONGODB_CONNECTION_STRING) - ) + context = MagicMock(spec=Context) + context.get_secret = MagicMock(return_value=TEST_MONGODB_CONNECTION_STRING) return context diff --git a/toolkits/postgres/arcade_postgres/__main__.py b/toolkits/postgres/arcade_postgres/__main__.py new file mode 100644 index 00000000..0b31719b --- /dev/null +++ b/toolkits/postgres/arcade_postgres/__main__.py @@ -0,0 +1,29 @@ +import sys +from typing import cast + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.mcp_app import TransportType + +import arcade_postgres + +app = MCPApp( + name="PostgreSQL", + instructions=( + "Use this server when you need to interact with PostgreSQL to help users " + "query, explore, and manage their PostgreSQL databases." + ), +) + +app.add_tools_from_module(arcade_postgres) + + +def main() -> None: + transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" + host = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1" + port = int(sys.argv[3]) if len(sys.argv) > 3 else 8000 + + app.run(transport=cast(TransportType, transport), host=host, port=port) + + +if __name__ == "__main__": + main() diff --git a/toolkits/postgres/arcade_postgres/database_engine.py b/toolkits/postgres/arcade_postgres/database_engine.py index e2aaf39c..2b2c86b7 100644 --- a/toolkits/postgres/arcade_postgres/database_engine.py +++ b/toolkits/postgres/arcade_postgres/database_engine.py @@ -1,7 +1,7 @@ from typing import Any, ClassVar from urllib.parse import urlparse -from arcade_tdk.errors import RetryableToolError +from arcade_mcp_server.exceptions import RetryableToolError from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine diff --git a/toolkits/postgres/arcade_postgres/tools/postgres.py b/toolkits/postgres/arcade_postgres/tools/postgres.py index 01bc9812..5bd79a2e 100644 --- a/toolkits/postgres/arcade_postgres/tools/postgres.py +++ b/toolkits/postgres/arcade_postgres/tools/postgres.py @@ -1,8 +1,8 @@ from typing import Annotated, Any +from arcade_mcp_server import Context, tool +from arcade_mcp_server.exceptions import RetryableToolError from arcade_mcp_server.metadata import Behavior, Operation, ToolMetadata -from arcade_tdk import ToolContext, tool -from arcade_tdk.errors import RetryableToolError from sqlalchemy import inspect, text from sqlalchemy.ext.asyncio import AsyncEngine @@ -22,7 +22,7 @@ from ..database_engine import MAX_ROWS_RETURNED, DatabaseEngine ), ) async def discover_schemas( - context: ToolContext, + context: Context, ) -> list[str]: """Discover all the schemas in the postgres database.""" async with await DatabaseEngine.get_engine( @@ -45,7 +45,7 @@ async def discover_schemas( ), ) async def discover_tables( - context: ToolContext, + context: Context, schema_name: Annotated[ str, "The database schema to discover tables in (default value: 'public')" ] = "public", @@ -74,7 +74,7 @@ async def discover_tables( ), ) async def get_table_schema( - context: ToolContext, + context: Context, schema_name: Annotated[str, "The database schema to get the table schema of"], table_name: Annotated[str, "The table to get the schema of"], ) -> list[str]: @@ -102,7 +102,7 @@ async def get_table_schema( ), ) async def execute_select_query( - context: ToolContext, + context: Context, select_clause: Annotated[ str, "This is the part of the SQL query that comes after the SELECT keyword wish a comma separated list of columns you wish to return. Do not include the SELECT keyword.", diff --git a/toolkits/postgres/evals/eval_postgres.py b/toolkits/postgres/evals/eval_postgres.py index ef2517e1..f54f77fe 100644 --- a/toolkits/postgres/evals/eval_postgres.py +++ b/toolkits/postgres/evals/eval_postgres.py @@ -1,4 +1,5 @@ import arcade_postgres +from arcade_core import ToolCatalog from arcade_evals import ( BinaryCritic, EvalRubric, @@ -12,7 +13,6 @@ from arcade_postgres.tools.postgres import ( execute_query, get_table_schema, ) -from arcade_tdk import ToolCatalog # Evaluation rubric rubric = EvalRubric( diff --git a/toolkits/postgres/pyproject.toml b/toolkits/postgres/pyproject.toml index 13dacb21..fe969c7a 100644 --- a/toolkits/postgres/pyproject.toml +++ b/toolkits/postgres/pyproject.toml @@ -4,11 +4,10 @@ build-backend = "hatchling.build" [project] name = "arcade_postgres" -version = "0.4.0" +version = "0.5.0" description = "Tools to query and explore a postgres database" requires-python = ">=3.10" dependencies = [ - "arcade-tdk>=3.0.0,<4.0.0", "arcade-mcp-server>=1.17.0,<2.0.0", "psycopg2-binary>=2.9.10", "pydantic>=2.11.7", @@ -21,11 +20,9 @@ dependencies = [ name = "evantahler" email = "support@arcade.dev" - [project.optional-dependencies] dev = [ "arcade-mcp[all]>=1.2.0,<2.0.0", - "arcade-serve>=3.0.0,<4.0.0", "pytest>=8.3.0,<8.4.0", "pytest-cov>=4.0.0,<4.1.0", "pytest-mock>=3.11.1,<3.12.0", @@ -36,14 +33,15 @@ dev = [ "ruff>=0.7.4,<0.8.0", ] +[project.scripts] +arcade-postgres = "arcade_postgres.__main__:main" +arcade_postgres = "arcade_postgres.__main__:main" + # Use local path sources for arcade libs when working locally [tool.uv.sources] arcade-mcp = { path = "../../", editable = true } -arcade-serve = { path = "../../libs/arcade-serve/", editable = true } -arcade-tdk = { path = "../../libs/arcade-tdk/", editable = true } arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true } - [tool.mypy] files = [ "arcade_postgres/**/*.py",] python_version = "3.10" diff --git a/toolkits/postgres/tests/test_postgres.py b/toolkits/postgres/tests/test_postgres.py index b9904664..99c35a80 100644 --- a/toolkits/postgres/tests/test_postgres.py +++ b/toolkits/postgres/tests/test_postgres.py @@ -1,8 +1,11 @@ import os from os import environ +from unittest.mock import MagicMock import pytest import pytest_asyncio +from arcade_mcp_server import Context +from arcade_mcp_server.exceptions import RetryableToolError from arcade_postgres.tools.postgres import ( DatabaseEngine, discover_schemas, @@ -10,8 +13,6 @@ from arcade_postgres.tools.postgres import ( execute_select_query, get_table_schema, ) -from arcade_tdk import ToolContext, ToolSecretItem -from arcade_tdk.errors import RetryableToolError from sqlalchemy import text from sqlalchemy.ext.asyncio import create_async_engine @@ -23,14 +24,8 @@ POSTGRES_DATABASE_CONNECTION_STRING = ( @pytest.fixture def mock_context(): - context = ToolContext() - context.secrets = [] - context.secrets.append( - ToolSecretItem( - key="POSTGRES_DATABASE_CONNECTION_STRING", value=POSTGRES_DATABASE_CONNECTION_STRING - ) - ) - + context = MagicMock(spec=Context) + context.get_secret = MagicMock(return_value=POSTGRES_DATABASE_CONNECTION_STRING) return context diff --git a/toolkits/zendesk/arcade_zendesk/__main__.py b/toolkits/zendesk/arcade_zendesk/__main__.py new file mode 100644 index 00000000..20a43288 --- /dev/null +++ b/toolkits/zendesk/arcade_zendesk/__main__.py @@ -0,0 +1,28 @@ +import sys +from typing import cast + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.mcp_app import TransportType + +import arcade_zendesk + +app = MCPApp( + name="Zendesk", + instructions=( + "Use this server when you need to interact with Zendesk to help users " + "manage support tickets and search knowledge base articles." + ), +) + +app.add_tools_from_module(arcade_zendesk) + + +def main() -> None: + transport = sys.argv[1] if len(sys.argv) > 1 else "stdio" + host = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1" + port = int(sys.argv[3]) if len(sys.argv) > 3 else 8000 + app.run(transport=cast(TransportType, transport), host=host, port=port) + + +if __name__ == "__main__": + main() diff --git a/toolkits/zendesk/arcade_zendesk/tools/search_articles.py b/toolkits/zendesk/arcade_zendesk/tools/search_articles.py index 4b4ac314..6d9bd539 100644 --- a/toolkits/zendesk/arcade_zendesk/tools/search_articles.py +++ b/toolkits/zendesk/arcade_zendesk/tools/search_articles.py @@ -2,6 +2,9 @@ import logging from typing import Annotated, Any import httpx +from arcade_mcp_server import Context, tool +from arcade_mcp_server.auth import OAuth2 +from arcade_mcp_server.exceptions import RetryableToolError from arcade_mcp_server.metadata import ( Behavior, Classification, @@ -9,9 +12,6 @@ from arcade_mcp_server.metadata import ( ServiceDomain, ToolMetadata, ) -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import OAuth2 -from arcade_tdk.errors import RetryableToolError from arcade_zendesk.enums import ArticleSortBy, SortOrder from arcade_zendesk.utils import ( @@ -41,7 +41,7 @@ logger = logging.getLogger(__name__) ), ) async def search_articles( - context: ToolContext, + context: Context, query: Annotated[ str | None, "Search text to match against articles. Supports quoted expressions for exact matching", diff --git a/toolkits/zendesk/arcade_zendesk/tools/system_context.py b/toolkits/zendesk/arcade_zendesk/tools/system_context.py index fc9b08f7..a74f18d1 100644 --- a/toolkits/zendesk/arcade_zendesk/tools/system_context.py +++ b/toolkits/zendesk/arcade_zendesk/tools/system_context.py @@ -1,5 +1,7 @@ from typing import Annotated, Any +from arcade_mcp_server import Context, tool +from arcade_mcp_server.auth import OAuth2 from arcade_mcp_server.metadata import ( Behavior, Classification, @@ -7,8 +9,6 @@ from arcade_mcp_server.metadata import ( ServiceDomain, ToolMetadata, ) -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import OAuth2 from arcade_zendesk.who_am_i_util import build_who_am_i_response @@ -30,7 +30,7 @@ from arcade_zendesk.who_am_i_util import build_who_am_i_response ), ) async def who_am_i( - context: ToolContext, + context: Context, ) -> Annotated[ dict[str, Any], "Get comprehensive user profile and Zendesk account information.", diff --git a/toolkits/zendesk/arcade_zendesk/tools/tickets.py b/toolkits/zendesk/arcade_zendesk/tools/tickets.py index d18514f4..d5ca6774 100644 --- a/toolkits/zendesk/arcade_zendesk/tools/tickets.py +++ b/toolkits/zendesk/arcade_zendesk/tools/tickets.py @@ -1,6 +1,9 @@ from typing import Annotated, Any import httpx +from arcade_mcp_server import Context, tool +from arcade_mcp_server.auth import OAuth2 +from arcade_mcp_server.exceptions import RetryableToolError from arcade_mcp_server.metadata import ( Behavior, Classification, @@ -8,9 +11,6 @@ from arcade_mcp_server.metadata import ( ServiceDomain, ToolMetadata, ) -from arcade_tdk import ToolContext, tool -from arcade_tdk.auth import OAuth2 -from arcade_tdk.errors import RetryableToolError from arcade_zendesk.enums import SortOrder, TicketStatus from arcade_zendesk.utils import fetch_paginated_results, get_zendesk_subdomain @@ -44,7 +44,7 @@ def _handle_ticket_not_found(response: httpx.Response, ticket_id: int) -> None: ), ) async def list_tickets( - context: ToolContext, + context: Context, status: Annotated[ TicketStatus, "The status of tickets to filter by. Defaults to 'open'", @@ -166,7 +166,7 @@ async def list_tickets( ), ) async def get_ticket_comments( - context: ToolContext, + context: Context, ticket_id: Annotated[int, "The ID of the ticket to get comments for"], ) -> Annotated[ dict[str, Any], "A dictionary containing the ticket comments, metadata, and ticket URL" @@ -229,7 +229,7 @@ async def get_ticket_comments( ), ) async def add_ticket_comment( - context: ToolContext, + context: Context, ticket_id: Annotated[int, "The ID of the ticket to comment on"], comment_body: Annotated[str, "The text of the comment"], public: Annotated[ @@ -300,7 +300,7 @@ async def add_ticket_comment( ), ) async def mark_ticket_solved( - context: ToolContext, + context: Context, ticket_id: Annotated[int, "The ID of the ticket to mark as solved"], comment_body: Annotated[ str | None, diff --git a/toolkits/zendesk/arcade_zendesk/utils.py b/toolkits/zendesk/arcade_zendesk/utils.py index 9ac405a3..8d8498d5 100644 --- a/toolkits/zendesk/arcade_zendesk/utils.py +++ b/toolkits/zendesk/arcade_zendesk/utils.py @@ -3,8 +3,8 @@ import re from typing import Any import httpx -from arcade_tdk import ToolContext -from arcade_tdk.errors import ToolExecutionError +from arcade_mcp_server import Context +from arcade_mcp_server.exceptions import ToolExecutionError from bs4 import BeautifulSoup logger = logging.getLogger(__name__) @@ -189,7 +189,7 @@ def validate_date_format(date_string: str) -> bool: return False -def get_zendesk_subdomain(context: ToolContext) -> str: +def get_zendesk_subdomain(context: Context) -> str: """ Get the Zendesk subdomain from secrets with proper error handling. diff --git a/toolkits/zendesk/arcade_zendesk/who_am_i_util.py b/toolkits/zendesk/arcade_zendesk/who_am_i_util.py index 5eb97bbd..6f773da5 100644 --- a/toolkits/zendesk/arcade_zendesk/who_am_i_util.py +++ b/toolkits/zendesk/arcade_zendesk/who_am_i_util.py @@ -1,7 +1,7 @@ from typing import Any, TypedDict import httpx -from arcade_tdk import ToolContext +from arcade_mcp_server import Context class WhoAmIResponse(TypedDict, total=False): @@ -19,7 +19,7 @@ class WhoAmIResponse(TypedDict, total=False): zendesk_access: bool -async def build_who_am_i_response(context: ToolContext) -> WhoAmIResponse: +async def build_who_am_i_response(context: Context) -> WhoAmIResponse: """Build comprehensive who am I response for Zendesk.""" user_info = await _get_current_user(context) organization_info = await _get_organization_info(context, user_info.get("organization_id")) @@ -32,7 +32,7 @@ async def build_who_am_i_response(context: ToolContext) -> WhoAmIResponse: return response_data # type: ignore[return-value] -async def _get_current_user(context: ToolContext) -> dict[str, Any]: +async def _get_current_user(context: Context) -> dict[str, Any]: """Get current user information from Zendesk API.""" subdomain = context.get_secret("ZENDESK_SUBDOMAIN") base_url = f"https://{subdomain}.zendesk.com" @@ -48,9 +48,7 @@ async def _get_current_user(context: ToolContext) -> dict[str, Any]: return response.json().get("user", {}) # type: ignore[no-any-return] -async def _get_organization_info( - context: ToolContext, organization_id: int | None -) -> dict[str, Any]: +async def _get_organization_info(context: Context, organization_id: int | None) -> dict[str, Any]: """Get organization information from Zendesk API.""" if not organization_id: return {} diff --git a/toolkits/zendesk/evals/eval_articles.py b/toolkits/zendesk/evals/eval_articles.py index c96d84d6..26c6b2dc 100644 --- a/toolkits/zendesk/evals/eval_articles.py +++ b/toolkits/zendesk/evals/eval_articles.py @@ -1,5 +1,6 @@ from datetime import timedelta +from arcade_core import ToolCatalog from arcade_evals import ( DatetimeCritic, EvalRubric, @@ -8,7 +9,6 @@ from arcade_evals import ( tool_eval, ) from arcade_evals.critic import BinaryCritic, SimilarityCritic -from arcade_tdk import ToolCatalog import arcade_zendesk from arcade_zendesk.enums import ArticleSortBy, SortOrder diff --git a/toolkits/zendesk/evals/eval_tickets.py b/toolkits/zendesk/evals/eval_tickets.py index 050407e0..628dd503 100644 --- a/toolkits/zendesk/evals/eval_tickets.py +++ b/toolkits/zendesk/evals/eval_tickets.py @@ -1,3 +1,4 @@ +from arcade_core import ToolCatalog from arcade_evals import ( BinaryCritic, EvalRubric, @@ -6,7 +7,6 @@ from arcade_evals import ( SimilarityCritic, tool_eval, ) -from arcade_tdk import ToolCatalog import arcade_zendesk from arcade_zendesk.enums import SortOrder, TicketStatus diff --git a/toolkits/zendesk/pyproject.toml b/toolkits/zendesk/pyproject.toml index 57519054..52cbc071 100644 --- a/toolkits/zendesk/pyproject.toml +++ b/toolkits/zendesk/pyproject.toml @@ -4,20 +4,21 @@ build-backend = "hatchling.build" [project] name = "arcade_zendesk" -version = "0.4.0" +version = "0.5.0" requires-python = ">=3.10" dependencies = [ - "arcade-tdk>=3.0.0,<4.0.0", "arcade-mcp-server>=1.17.0,<2.0.0", "httpx>=0.25.0,<1.0.0", "beautifulsoup4>=4.0.0,<5" ] +[project.scripts] +arcade-zendesk = "arcade_zendesk.__main__:main" +arcade_zendesk = "arcade_zendesk.__main__:main" [project.optional-dependencies] dev = [ "arcade-mcp[all]>=1.2.0,<2.0.0", - "arcade-serve>=3.0.0,<4.0.0", "pytest>=8.3.0,<8.4.0", "pytest-cov>=4.0.0,<4.1.0", "pytest-mock>=3.11.1,<3.12.0", @@ -31,8 +32,6 @@ dev = [ # Use local path sources for arcade libs when working locally [tool.uv.sources] arcade-mcp = { path = "../../", editable = true } -arcade-serve = { path = "../../libs/arcade-serve/", editable = true } -arcade-tdk = { path = "../../libs/arcade-tdk/", editable = true } arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true } diff --git a/toolkits/zendesk/tests/conftest.py b/toolkits/zendesk/tests/conftest.py index ad98f5cf..60d17f9a 100644 --- a/toolkits/zendesk/tests/conftest.py +++ b/toolkits/zendesk/tests/conftest.py @@ -1,13 +1,13 @@ from unittest.mock import AsyncMock, MagicMock import pytest -from arcade_tdk import ToolContext +from arcade_mcp_server import Context @pytest.fixture def mock_context(): """Standard mock context fixture used across all arcade toolkits.""" - context = MagicMock(spec=ToolContext) + context = MagicMock(spec=Context) context.get_auth_token_or_empty = MagicMock(return_value="fake-token") context.get_secret = MagicMock() diff --git a/toolkits/zendesk/tests/test_search_articles.py b/toolkits/zendesk/tests/test_search_articles.py index 0cb49d41..738831a0 100644 --- a/toolkits/zendesk/tests/test_search_articles.py +++ b/toolkits/zendesk/tests/test_search_articles.py @@ -1,5 +1,5 @@ import pytest -from arcade_tdk.errors import RetryableToolError, ToolExecutionError +from arcade_mcp_server.exceptions import RetryableToolError, ToolExecutionError from arcade_zendesk.enums import ArticleSortBy, SortOrder from arcade_zendesk.tools.search_articles import search_articles