Migrate OSS toolkits to MCPApp (#782)
<!-- CURSOR_SUMMARY --> > [!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`. > > <sup>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).</sup> <!-- /CURSOR_SUMMARY -->
This commit is contained in:
parent
36584942f7
commit
c50699d5e6
57 changed files with 361 additions and 176 deletions
28
toolkits/brightdata/arcade_brightdata/__main__.py
Normal file
28
toolkits/brightdata/arcade_brightdata/__main__.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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[
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
29
toolkits/clickhouse/arcade_clickhouse/__main__.py
Normal file
29
toolkits/clickhouse/arcade_clickhouse/__main__.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
28
toolkits/linkedin/arcade_linkedin/__main__.py
Normal file
28
toolkits/linkedin/arcade_linkedin/__main__.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
29
toolkits/math/arcade_math/__main__.py
Normal file
29
toolkits/math/arcade_math/__main__.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
29
toolkits/mongodb/arcade_mongodb/__main__.py
Normal file
29
toolkits/mongodb/arcade_mongodb/__main__.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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[
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
29
toolkits/postgres/arcade_postgres/__main__.py
Normal file
29
toolkits/postgres/arcade_postgres/__main__.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
28
toolkits/zendesk/arcade_zendesk/__main__.py
Normal file
28
toolkits/zendesk/arcade_zendesk/__main__.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue