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
|
from typing import Annotated, Any, cast
|
||||||
|
|
||||||
import requests
|
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 (
|
from arcade_mcp_server.metadata import (
|
||||||
Behavior,
|
Behavior,
|
||||||
Classification,
|
Classification,
|
||||||
|
|
@ -12,7 +13,6 @@ from arcade_mcp_server.metadata import (
|
||||||
ServiceDomain,
|
ServiceDomain,
|
||||||
ToolMetadata,
|
ToolMetadata,
|
||||||
)
|
)
|
||||||
from arcade_tdk import ToolContext, tool
|
|
||||||
|
|
||||||
from arcade_brightdata.bright_data_client import BrightDataClient
|
from arcade_brightdata.bright_data_client import BrightDataClient
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ class SourceType(str, Enum):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def scrape_as_markdown(
|
def scrape_as_markdown(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
url: Annotated[str, "URL to scrape"],
|
url: Annotated[str, "URL to scrape"],
|
||||||
) -> Annotated[str, "Scraped webpage content as Markdown"]:
|
) -> Annotated[str, "Scraped webpage content as Markdown"]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -108,7 +108,7 @@ def scrape_as_markdown(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def search_engine( # noqa: C901
|
def search_engine( # noqa: C901
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
query: Annotated[str, "Search query"],
|
query: Annotated[str, "Search query"],
|
||||||
engine: Annotated[SearchEngine, "Search engine to use"] = SearchEngine.GOOGLE,
|
engine: Annotated[SearchEngine, "Search engine to use"] = SearchEngine.GOOGLE,
|
||||||
language: Annotated[str | None, "Two-letter language code"] = None,
|
language: Annotated[str | None, "Two-letter language code"] = None,
|
||||||
|
|
@ -218,7 +218,7 @@ def search_engine( # noqa: C901
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def web_data_feed(
|
def web_data_feed(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
source_type: Annotated[SourceType, "Type of data source"],
|
source_type: Annotated[SourceType, "Type of data source"],
|
||||||
url: Annotated[str, "URL of the web resource to extract data from"],
|
url: Annotated[str, "URL of the web resource to extract data from"],
|
||||||
num_of_reviews: Annotated[
|
num_of_reviews: Annotated[
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "arcade_brightdata"
|
name = "arcade_brightdata"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
description = "Search, Crawl and Scrape any site, at scale, without getting blocked"
|
description = "Search, Crawl and Scrape any site, at scale, without getting blocked"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arcade-tdk>=3.0.0,<4.0.0",
|
|
||||||
"arcade-mcp-server>=1.17.0,<2.0.0",
|
"arcade-mcp-server>=1.17.0,<2.0.0",
|
||||||
"requests>=2.32.5",
|
"requests>=2.32.5",
|
||||||
]
|
]
|
||||||
|
|
@ -16,10 +15,13 @@ dependencies = [
|
||||||
name = "meirk-brd"
|
name = "meirk-brd"
|
||||||
email = "meirk@brightdata.com"
|
email = "meirk@brightdata.com"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
arcade-brightdata = "arcade_brightdata.__main__:main"
|
||||||
|
arcade_brightdata = "arcade_brightdata.__main__:main"
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
"arcade-mcp[all]>=1.2.0,<2.0.0",
|
"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>=8.3.0,<8.4.0",
|
||||||
"pytest-cov>=4.0.0,<4.1.0",
|
"pytest-cov>=4.0.0,<4.1.0",
|
||||||
"pytest-mock>=3.11.1,<3.12.0",
|
"pytest-mock>=3.11.1,<3.12.0",
|
||||||
|
|
@ -48,8 +50,6 @@ ignore_missing_imports = "True"
|
||||||
|
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
arcade-mcp = { path = "../../", editable = true }
|
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 }
|
arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true }
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
from os import environ
|
from os import environ
|
||||||
|
from unittest.mock import MagicMock as _MagicMock
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
from arcade_tdk import ToolContext, ToolSecretItem
|
from arcade_mcp_server import Context
|
||||||
from arcade_tdk.errors import ToolExecutionError
|
from arcade_mcp_server.exceptions import ToolExecutionError
|
||||||
|
|
||||||
from arcade_brightdata.bright_data_client import BrightDataClient
|
from arcade_brightdata.bright_data_client import BrightDataClient
|
||||||
from arcade_brightdata.tools.bright_data_tools import (
|
from arcade_brightdata.tools.bright_data_tools import (
|
||||||
|
|
@ -21,10 +22,13 @@ BRIGHTDATA_ZONE = environ.get("TEST_BRIGHTDATA_ZONE") or "unblocker"
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_context():
|
def mock_context():
|
||||||
context = ToolContext()
|
context = _MagicMock(spec=Context)
|
||||||
context.secrets = []
|
context.get_secret = _MagicMock(
|
||||||
context.secrets.append(ToolSecretItem(key="BRIGHTDATA_API_KEY", value=BRIGHTDATA_API_KEY))
|
side_effect=lambda key: {
|
||||||
context.secrets.append(ToolSecretItem(key="BRIGHTDATA_ZONE", value=BRIGHTDATA_ZONE))
|
"BRIGHTDATA_API_KEY": BRIGHTDATA_API_KEY,
|
||||||
|
"BRIGHTDATA_ZONE": BRIGHTDATA_ZONE,
|
||||||
|
}[key]
|
||||||
|
)
|
||||||
return context
|
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
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import clickhouse_connect
|
import clickhouse_connect
|
||||||
from arcade_tdk.errors import RetryableToolError
|
from arcade_mcp_server.exceptions import RetryableToolError
|
||||||
|
|
||||||
MAX_ROWS_RETURNED = 1000
|
MAX_ROWS_RETURNED = 1000
|
||||||
TEST_QUERY = "SELECT 1"
|
TEST_QUERY = "SELECT 1"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from typing import Annotated, Any
|
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_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
|
from ..database_engine import MAX_ROWS_RETURNED, DatabaseEngine
|
||||||
|
|
||||||
|
|
@ -20,7 +20,7 @@ from ..database_engine import MAX_ROWS_RETURNED, DatabaseEngine
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def discover_schemas(
|
async def discover_schemas(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""Discover all the schemas in the ClickHouse database.
|
"""Discover all the schemas in the ClickHouse database.
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ async def discover_schemas(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def discover_databases(
|
async def discover_databases(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""Discover all the databases in the ClickHouse database."""
|
"""Discover all the databases in the ClickHouse database."""
|
||||||
async with await DatabaseEngine.get_engine(
|
async with await DatabaseEngine.get_engine(
|
||||||
|
|
@ -66,7 +66,7 @@ async def discover_databases(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def discover_tables(
|
async def discover_tables(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""Discover all the tables in the ClickHouse database when the list of tables is not known.
|
"""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(
|
async def get_table_schema(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
schema_name: Annotated[str, "The schema to get the table schema of"],
|
schema_name: Annotated[str, "The schema to get the table schema of"],
|
||||||
table_name: Annotated[str, "The table to get the schema of"],
|
table_name: Annotated[str, "The table to get the schema of"],
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
|
|
@ -120,7 +120,7 @@ async def get_table_schema(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def execute_select_query(
|
async def execute_select_query(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
select_clause: Annotated[
|
select_clause: Annotated[
|
||||||
str,
|
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.",
|
"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]
|
[project]
|
||||||
name = "arcade_clickhouse"
|
name = "arcade_clickhouse"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
description = "Tools to query and explore a ClickHouse database"
|
description = "Tools to query and explore a ClickHouse database"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arcade-tdk>=3.0.0,<4.0.0",
|
|
||||||
"arcade-mcp-server>=1.17.0,<2.0.0",
|
"arcade-mcp-server>=1.17.0,<2.0.0",
|
||||||
"clickhouse-connect>=0.7.0",
|
"clickhouse-connect>=0.7.0",
|
||||||
"pydantic>=2.11.7",
|
"pydantic>=2.11.7",
|
||||||
|
|
@ -22,11 +21,9 @@ dependencies = [
|
||||||
name = "evantahler"
|
name = "evantahler"
|
||||||
email = "support@arcade.dev"
|
email = "support@arcade.dev"
|
||||||
|
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
"arcade-mcp[all]>=1.2.0,<2.0.0",
|
"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>=8.3.0,<8.4.0",
|
||||||
"pytest-cov>=4.0.0,<4.1.0",
|
"pytest-cov>=4.0.0,<4.1.0",
|
||||||
"pytest-mock>=3.11.1,<3.12.0",
|
"pytest-mock>=3.11.1,<3.12.0",
|
||||||
|
|
@ -37,14 +34,15 @@ dev = [
|
||||||
"ruff>=0.7.4,<0.8.0",
|
"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
|
# Use local path sources for arcade libs when working locally
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
arcade-mcp = { path = "../../", editable = true }
|
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 }
|
arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true }
|
||||||
|
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
files = [ "arcade_clickhouse/**/*.py",]
|
files = [ "arcade_clickhouse/**/*.py",]
|
||||||
python_version = "3.10"
|
python_version = "3.10"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
from os import environ
|
from os import environ
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
@ -10,8 +11,8 @@ from arcade_clickhouse.tools.clickhouse import (
|
||||||
execute_select_query,
|
execute_select_query,
|
||||||
get_table_schema,
|
get_table_schema,
|
||||||
)
|
)
|
||||||
from arcade_tdk import ToolContext, ToolSecretItem
|
from arcade_mcp_server import Context
|
||||||
from arcade_tdk.errors import RetryableToolError
|
from arcade_mcp_server.exceptions import RetryableToolError
|
||||||
|
|
||||||
CLICKHOUSE_DATABASE_CONNECTION_STRING = (
|
CLICKHOUSE_DATABASE_CONNECTION_STRING = (
|
||||||
environ.get("TEST_CLICKHOUSE_DATABASE_CONNECTION_STRING")
|
environ.get("TEST_CLICKHOUSE_DATABASE_CONNECTION_STRING")
|
||||||
|
|
@ -21,14 +22,8 @@ CLICKHOUSE_DATABASE_CONNECTION_STRING = (
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_context():
|
def mock_context():
|
||||||
context = ToolContext()
|
context = MagicMock(spec=Context)
|
||||||
context.secrets = []
|
context.get_secret = MagicMock(return_value=CLICKHOUSE_DATABASE_CONNECTION_STRING)
|
||||||
context.secrets.append(
|
|
||||||
ToolSecretItem(
|
|
||||||
key="CLICKHOUSE_DATABASE_CONNECTION_STRING", value=CLICKHOUSE_DATABASE_CONNECTION_STRING
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return context
|
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 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 (
|
from arcade_mcp_server.metadata import (
|
||||||
Behavior,
|
Behavior,
|
||||||
Classification,
|
Classification,
|
||||||
|
|
@ -7,9 +10,6 @@ from arcade_mcp_server.metadata import (
|
||||||
ServiceDomain,
|
ServiceDomain,
|
||||||
ToolMetadata,
|
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
|
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(
|
async def create_text_post(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
text: Annotated[str, "The text content of the post"],
|
text: Annotated[str, "The text content of the post"],
|
||||||
) -> Annotated[str, "URL of the shared post"]:
|
) -> Annotated[str, "URL of the shared post"]:
|
||||||
"""Share a new text post to LinkedIn."""
|
"""Share a new text post to LinkedIn."""
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import httpx
|
import httpx
|
||||||
from arcade_tdk import ToolContext
|
from arcade_mcp_server import Context
|
||||||
from arcade_tdk.errors import ToolExecutionError
|
from arcade_mcp_server.exceptions import ToolExecutionError
|
||||||
|
|
||||||
from arcade_linkedin.tools.constants import LINKEDIN_BASE_URL
|
from arcade_linkedin.tools.constants import LINKEDIN_BASE_URL
|
||||||
|
|
||||||
|
|
||||||
async def _send_linkedin_request(
|
async def _send_linkedin_request(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
method: str,
|
method: str,
|
||||||
endpoint: str,
|
endpoint: str,
|
||||||
params: dict | None = None,
|
params: dict | None = None,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from arcade_tdk import ToolAuthorizationContext, ToolContext
|
from arcade_mcp_server import Context
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def tool_context():
|
def tool_context():
|
||||||
"""Fixture for the ToolContext with mock authorization."""
|
"""Fixture for the tool Context with mock authorization."""
|
||||||
return ToolContext(
|
context = MagicMock(spec=Context)
|
||||||
authorization=ToolAuthorizationContext(token="test_token", user_info={"sub": "test_user"}), # noqa: S106
|
authorization = MagicMock()
|
||||||
user_id="test_user",
|
authorization.token = "test_token" # noqa: S105
|
||||||
)
|
authorization.user_info = {"sub": "test_user"}
|
||||||
|
context.authorization = authorization
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from arcade_core import ToolCatalog
|
||||||
from arcade_evals import (
|
from arcade_evals import (
|
||||||
EvalRubric,
|
EvalRubric,
|
||||||
EvalSuite,
|
EvalSuite,
|
||||||
|
|
@ -5,7 +6,6 @@ from arcade_evals import (
|
||||||
SimilarityCritic,
|
SimilarityCritic,
|
||||||
tool_eval,
|
tool_eval,
|
||||||
)
|
)
|
||||||
from arcade_tdk import ToolCatalog
|
|
||||||
|
|
||||||
import arcade_linkedin
|
import arcade_linkedin
|
||||||
from arcade_linkedin.tools.share import create_text_post
|
from arcade_linkedin.tools.share import create_text_post
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "arcade_linkedin"
|
name = "arcade_linkedin"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
description = "Arcade.dev LLM tools for LinkedIn"
|
description = "Arcade.dev LLM tools for LinkedIn"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arcade-tdk>=3.0.0,<4.0.0",
|
|
||||||
"arcade-mcp-server>=1.17.0,<2.0.0",
|
"arcade-mcp-server>=1.17.0,<2.0.0",
|
||||||
"httpx>=0.27.2,<1.0.0",
|
"httpx>=0.27.2,<1.0.0",
|
||||||
]
|
]
|
||||||
|
|
@ -16,10 +15,13 @@ dependencies = [
|
||||||
name = "Arcade"
|
name = "Arcade"
|
||||||
email = "dev@arcade.dev"
|
email = "dev@arcade.dev"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
arcade-linkedin = "arcade_linkedin.__main__:main"
|
||||||
|
arcade_linkedin = "arcade_linkedin.__main__:main"
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
"arcade-mcp[all]>=1.2.0,<2.0.0",
|
"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>=8.3.0,<8.4.0",
|
||||||
"pytest-cov>=4.0.0,<4.1.0",
|
"pytest-cov>=4.0.0,<4.1.0",
|
||||||
"pytest-asyncio>=0.24.0,<0.25.0",
|
"pytest-asyncio>=0.24.0,<0.25.0",
|
||||||
|
|
@ -33,8 +35,6 @@ dev = [
|
||||||
# Use local path sources for arcade libs when working locally
|
# Use local path sources for arcade libs when working locally
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
arcade-mcp = {path = "../../", editable = true}
|
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 }
|
arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true }
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from unittest.mock import AsyncMock, MagicMock
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from arcade_tdk.errors import ToolExecutionError
|
from arcade_mcp_server.exceptions import ToolExecutionError
|
||||||
|
|
||||||
from arcade_linkedin.tools.share import create_text_post
|
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 decimal import Decimal
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
|
from arcade_mcp_server import tool
|
||||||
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
||||||
from arcade_tdk import tool
|
|
||||||
|
|
||||||
decimal.getcontext().prec = 100
|
decimal.getcontext().prec = 100
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import math
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
|
from arcade_mcp_server import tool
|
||||||
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
||||||
from arcade_tdk import tool
|
|
||||||
|
|
||||||
decimal.getcontext().prec = 100
|
decimal.getcontext().prec = 100
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import math
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
|
from arcade_mcp_server import tool
|
||||||
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
||||||
from arcade_tdk import tool
|
|
||||||
|
|
||||||
decimal.getcontext().prec = 100
|
decimal.getcontext().prec = 100
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import random
|
import random
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
|
from arcade_mcp_server import tool
|
||||||
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
||||||
from arcade_tdk import tool
|
|
||||||
|
|
||||||
|
|
||||||
@tool(
|
@tool(
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import math
|
import math
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
|
from arcade_mcp_server import tool
|
||||||
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
||||||
from arcade_tdk import tool
|
|
||||||
|
|
||||||
|
|
||||||
@tool(
|
@tool(
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import math
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
|
from arcade_mcp_server import tool
|
||||||
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
||||||
from arcade_tdk import tool
|
|
||||||
|
|
||||||
decimal.getcontext().prec = 100
|
decimal.getcontext().prec = 100
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ from decimal import Decimal
|
||||||
from statistics import median as stats_median
|
from statistics import median as stats_median
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
|
from arcade_mcp_server import tool
|
||||||
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
||||||
from arcade_tdk import tool
|
|
||||||
|
|
||||||
decimal.getcontext().prec = 100
|
decimal.getcontext().prec = 100
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import math
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
|
from arcade_mcp_server import tool
|
||||||
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
from arcade_mcp_server.metadata import Behavior, ToolMetadata
|
||||||
from arcade_tdk import tool
|
|
||||||
|
|
||||||
decimal.getcontext().prec = 100
|
decimal.getcontext().prec = 100
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from arcade_core import ToolCatalog
|
||||||
from arcade_evals import (
|
from arcade_evals import (
|
||||||
BinaryCritic,
|
BinaryCritic,
|
||||||
EvalRubric,
|
EvalRubric,
|
||||||
|
|
@ -8,7 +9,6 @@ from arcade_evals import (
|
||||||
ExpectedToolCall,
|
ExpectedToolCall,
|
||||||
tool_eval,
|
tool_eval,
|
||||||
)
|
)
|
||||||
from arcade_tdk import ToolCatalog
|
|
||||||
|
|
||||||
import arcade_math
|
import arcade_math
|
||||||
from arcade_math.tools.arithmetic import (
|
from arcade_math.tools.arithmetic import (
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "arcade_math"
|
name = "arcade_math"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
description = "Arcade.dev LLM tools for doing math"
|
description = "Arcade.dev LLM tools for doing math"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arcade-tdk>=3.0.0,<4.0.0",
|
|
||||||
"arcade-mcp-server>=1.17.0,<2.0.0",
|
"arcade-mcp-server>=1.17.0,<2.0.0",
|
||||||
]
|
]
|
||||||
[[project.authors]]
|
[[project.authors]]
|
||||||
|
|
@ -18,7 +17,6 @@ email = "dev@arcade.dev"
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
"arcade-mcp[all]>=1.2.0,<2.0.0",
|
"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>=8.3.0,<8.4.0",
|
||||||
"pytest-cov>=4.0.0,<4.1.0",
|
"pytest-cov>=4.0.0,<4.1.0",
|
||||||
"pytest-asyncio>=0.24.0,<0.25.0",
|
"pytest-asyncio>=0.24.0,<0.25.0",
|
||||||
|
|
@ -29,11 +27,13 @@ dev = [
|
||||||
"ruff>=0.7.4,<0.8.0",
|
"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
|
# Use local path sources for arcade libs when working locally
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
arcade-mcp = {path = "../../", editable = true}
|
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 }
|
arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true }
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
from arcade_tdk.errors import ToolExecutionError
|
from arcade_mcp_server.exceptions import ToolExecutionError
|
||||||
|
|
||||||
from arcade_math.tools.arithmetic import (
|
from arcade_math.tools.arithmetic import (
|
||||||
add,
|
add,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
from arcade_tdk.errors import ToolExecutionError
|
from arcade_mcp_server.exceptions import ToolExecutionError
|
||||||
|
|
||||||
from arcade_math.tools.exponents import (
|
from arcade_math.tools.exponents import (
|
||||||
log,
|
log,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
from arcade_tdk.errors import ToolExecutionError
|
from arcade_mcp_server.exceptions import ToolExecutionError
|
||||||
|
|
||||||
from arcade_math.tools.miscellaneous import (
|
from arcade_math.tools.miscellaneous import (
|
||||||
abs_val,
|
abs_val,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
from arcade_tdk.errors import ToolExecutionError
|
from arcade_mcp_server.exceptions import ToolExecutionError
|
||||||
|
|
||||||
from arcade_math.tools.rational import (
|
from arcade_math.tools.rational import (
|
||||||
gcd,
|
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 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 motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
|
||||||
from pymongo.errors import ServerSelectionTimeoutError
|
from pymongo.errors import ServerSelectionTimeoutError
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import json
|
import json
|
||||||
from typing import Annotated, Any
|
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_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 ..database_engine import MAX_RECORDS_RETURNED, DatabaseEngine
|
||||||
from .utils import (
|
from .utils import (
|
||||||
|
|
@ -34,7 +34,7 @@ from .utils import (
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def discover_databases(
|
async def discover_databases(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""Discover all the databases in the MongoDB instance."""
|
"""Discover all the databases in the MongoDB instance."""
|
||||||
client = await DatabaseEngine.get_instance(context.get_secret("MONGODB_CONNECTION_STRING"))
|
client = await DatabaseEngine.get_instance(context.get_secret("MONGODB_CONNECTION_STRING"))
|
||||||
|
|
@ -57,7 +57,7 @@ async def discover_databases(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def discover_collections(
|
async def discover_collections(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
database_name: Annotated[str, "The database name to discover collections in"],
|
database_name: Annotated[str, "The database name to discover collections in"],
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""Discover all the collections in the MongoDB database when the list of collections is not known.
|
"""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(
|
async def get_collection_schema(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
database_name: Annotated[str, "The database name to get the collection schema of"],
|
database_name: Annotated[str, "The database name to get the collection schema of"],
|
||||||
collection_name: Annotated[str, "The collection to get the schema of"],
|
collection_name: Annotated[str, "The collection to get the schema of"],
|
||||||
sample_size: Annotated[
|
sample_size: Annotated[
|
||||||
|
|
@ -137,7 +137,7 @@ async def get_collection_schema(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def find_documents(
|
async def find_documents(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
database_name: Annotated[str, "The database name to query"],
|
database_name: Annotated[str, "The database name to query"],
|
||||||
collection_name: Annotated[str, "The collection name to query"],
|
collection_name: Annotated[str, "The collection name to query"],
|
||||||
filter_dict: Annotated[
|
filter_dict: Annotated[
|
||||||
|
|
@ -251,7 +251,7 @@ async def find_documents(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def count_documents(
|
async def count_documents(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
database_name: Annotated[str, "The database name to query"],
|
database_name: Annotated[str, "The database name to query"],
|
||||||
collection_name: Annotated[str, "The collection name to query"],
|
collection_name: Annotated[str, "The collection name to query"],
|
||||||
filter_dict: Annotated[
|
filter_dict: Annotated[
|
||||||
|
|
@ -299,7 +299,7 @@ async def count_documents(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def aggregate_documents(
|
async def aggregate_documents(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
database_name: Annotated[str, "The database name to query"],
|
database_name: Annotated[str, "The database name to query"],
|
||||||
collection_name: Annotated[str, "The collection name to query"],
|
collection_name: Annotated[str, "The collection name to query"],
|
||||||
pipeline: Annotated[
|
pipeline: Annotated[
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from arcade_tdk.errors import RetryableToolError
|
from arcade_mcp_server.exceptions import RetryableToolError
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# RUN ME WITH `uv run arcade evals evals --host api.arcade.dev`
|
# RUN ME WITH `uv run arcade evals evals --host api.arcade.dev`
|
||||||
|
|
||||||
import arcade_mongodb
|
import arcade_mongodb
|
||||||
|
from arcade_core import ToolCatalog
|
||||||
from arcade_evals import (
|
from arcade_evals import (
|
||||||
BinaryCritic,
|
BinaryCritic,
|
||||||
EvalRubric,
|
EvalRubric,
|
||||||
|
|
@ -17,7 +18,6 @@ from arcade_mongodb.tools.mongodb import (
|
||||||
find_documents,
|
find_documents,
|
||||||
get_collection_schema,
|
get_collection_schema,
|
||||||
)
|
)
|
||||||
from arcade_tdk import ToolCatalog
|
|
||||||
|
|
||||||
# Evaluation rubric
|
# Evaluation rubric
|
||||||
rubric = EvalRubric(
|
rubric = EvalRubric(
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "arcade_mongodb"
|
name = "arcade_mongodb"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
description = "Tools to query and explore a MongoDB database"
|
description = "Tools to query and explore a MongoDB database"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arcade-tdk>=3.0.0,<4.0.0",
|
|
||||||
"arcade-mcp-server>=1.17.0,<2.0.0",
|
"arcade-mcp-server>=1.17.0,<2.0.0",
|
||||||
"pymongo>=4.10.1",
|
"pymongo>=4.10.1",
|
||||||
"pydantic>=2.11.7",
|
"pydantic>=2.11.7",
|
||||||
|
|
@ -18,11 +17,9 @@ dependencies = [
|
||||||
name = "evantahler"
|
name = "evantahler"
|
||||||
email = "support@arcade.dev"
|
email = "support@arcade.dev"
|
||||||
|
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
"arcade-mcp[all]>=1.2.0,<2.0.0",
|
"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>=8.3.0,<8.4.0",
|
||||||
"pytest-cov>=4.0.0,<4.1.0",
|
"pytest-cov>=4.0.0,<4.1.0",
|
||||||
"pytest-mock>=3.11.1,<3.12.0",
|
"pytest-mock>=3.11.1,<3.12.0",
|
||||||
|
|
@ -33,14 +30,15 @@ dev = [
|
||||||
"ruff>=0.7.4,<0.8.0",
|
"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
|
# Use local path sources for arcade libs when working locally
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
arcade-mcp = { path = "../../", editable = true }
|
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 }
|
arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true }
|
||||||
|
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
files = [ "arcade_mongodb/**/*.py",]
|
files = [ "arcade_mongodb/**/*.py",]
|
||||||
python_version = "3.10"
|
python_version = "3.10"
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from arcade_core.errors import ToolExecutionError
|
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_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
|
from .conftest import TEST_MONGODB_CONNECTION_STRING
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_context():
|
def mock_context():
|
||||||
context = ToolContext()
|
context = MagicMock(spec=Context)
|
||||||
context.secrets = []
|
context.get_secret = MagicMock(return_value=TEST_MONGODB_CONNECTION_STRING)
|
||||||
context.secrets.append(
|
|
||||||
ToolSecretItem(key="MONGODB_CONNECTION_STRING", value=TEST_MONGODB_CONNECTION_STRING)
|
|
||||||
)
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import json
|
import json
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
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.database_engine import DatabaseEngine
|
||||||
from arcade_mongodb.tools.mongodb import (
|
from arcade_mongodb.tools.mongodb import (
|
||||||
# UserStatus,
|
# UserStatus,
|
||||||
|
|
@ -12,19 +15,14 @@ from arcade_mongodb.tools.mongodb import (
|
||||||
get_collection_schema,
|
get_collection_schema,
|
||||||
# update_user_status,
|
# update_user_status,
|
||||||
)
|
)
|
||||||
from arcade_tdk import ToolContext, ToolSecretItem
|
|
||||||
from arcade_tdk.errors import RetryableToolError
|
|
||||||
|
|
||||||
from .conftest import TEST_MONGODB_CONNECTION_STRING
|
from .conftest import TEST_MONGODB_CONNECTION_STRING
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_context():
|
def mock_context():
|
||||||
context = ToolContext()
|
context = MagicMock(spec=Context)
|
||||||
context.secrets = []
|
context.get_secret = MagicMock(return_value=TEST_MONGODB_CONNECTION_STRING)
|
||||||
context.secrets.append(
|
|
||||||
ToolSecretItem(key="MONGODB_CONNECTION_STRING", value=TEST_MONGODB_CONNECTION_STRING)
|
|
||||||
)
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
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_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
|
from .conftest import TEST_MONGODB_CONNECTION_STRING
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_context():
|
def mock_context():
|
||||||
context = ToolContext()
|
context = MagicMock(spec=Context)
|
||||||
context.secrets = []
|
context.get_secret = MagicMock(return_value=TEST_MONGODB_CONNECTION_STRING)
|
||||||
context.secrets.append(
|
|
||||||
ToolSecretItem(key="MONGODB_CONNECTION_STRING", value=TEST_MONGODB_CONNECTION_STRING)
|
|
||||||
)
|
|
||||||
return context
|
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 typing import Any, ClassVar
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from arcade_tdk.errors import RetryableToolError
|
from arcade_mcp_server.exceptions import RetryableToolError
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
|
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from typing import Annotated, Any
|
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_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 import inspect, text
|
||||||
from sqlalchemy.ext.asyncio import AsyncEngine
|
from sqlalchemy.ext.asyncio import AsyncEngine
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ from ..database_engine import MAX_ROWS_RETURNED, DatabaseEngine
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def discover_schemas(
|
async def discover_schemas(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""Discover all the schemas in the postgres database."""
|
"""Discover all the schemas in the postgres database."""
|
||||||
async with await DatabaseEngine.get_engine(
|
async with await DatabaseEngine.get_engine(
|
||||||
|
|
@ -45,7 +45,7 @@ async def discover_schemas(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def discover_tables(
|
async def discover_tables(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
schema_name: Annotated[
|
schema_name: Annotated[
|
||||||
str, "The database schema to discover tables in (default value: 'public')"
|
str, "The database schema to discover tables in (default value: 'public')"
|
||||||
] = "public",
|
] = "public",
|
||||||
|
|
@ -74,7 +74,7 @@ async def discover_tables(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def get_table_schema(
|
async def get_table_schema(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
schema_name: Annotated[str, "The database schema to get the table schema of"],
|
schema_name: Annotated[str, "The database schema to get the table schema of"],
|
||||||
table_name: Annotated[str, "The table to get the schema of"],
|
table_name: Annotated[str, "The table to get the schema of"],
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
|
|
@ -102,7 +102,7 @@ async def get_table_schema(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def execute_select_query(
|
async def execute_select_query(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
select_clause: Annotated[
|
select_clause: Annotated[
|
||||||
str,
|
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.",
|
"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
|
import arcade_postgres
|
||||||
|
from arcade_core import ToolCatalog
|
||||||
from arcade_evals import (
|
from arcade_evals import (
|
||||||
BinaryCritic,
|
BinaryCritic,
|
||||||
EvalRubric,
|
EvalRubric,
|
||||||
|
|
@ -12,7 +13,6 @@ from arcade_postgres.tools.postgres import (
|
||||||
execute_query,
|
execute_query,
|
||||||
get_table_schema,
|
get_table_schema,
|
||||||
)
|
)
|
||||||
from arcade_tdk import ToolCatalog
|
|
||||||
|
|
||||||
# Evaluation rubric
|
# Evaluation rubric
|
||||||
rubric = EvalRubric(
|
rubric = EvalRubric(
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "arcade_postgres"
|
name = "arcade_postgres"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
description = "Tools to query and explore a postgres database"
|
description = "Tools to query and explore a postgres database"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arcade-tdk>=3.0.0,<4.0.0",
|
|
||||||
"arcade-mcp-server>=1.17.0,<2.0.0",
|
"arcade-mcp-server>=1.17.0,<2.0.0",
|
||||||
"psycopg2-binary>=2.9.10",
|
"psycopg2-binary>=2.9.10",
|
||||||
"pydantic>=2.11.7",
|
"pydantic>=2.11.7",
|
||||||
|
|
@ -21,11 +20,9 @@ dependencies = [
|
||||||
name = "evantahler"
|
name = "evantahler"
|
||||||
email = "support@arcade.dev"
|
email = "support@arcade.dev"
|
||||||
|
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
"arcade-mcp[all]>=1.2.0,<2.0.0",
|
"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>=8.3.0,<8.4.0",
|
||||||
"pytest-cov>=4.0.0,<4.1.0",
|
"pytest-cov>=4.0.0,<4.1.0",
|
||||||
"pytest-mock>=3.11.1,<3.12.0",
|
"pytest-mock>=3.11.1,<3.12.0",
|
||||||
|
|
@ -36,14 +33,15 @@ dev = [
|
||||||
"ruff>=0.7.4,<0.8.0",
|
"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
|
# Use local path sources for arcade libs when working locally
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
arcade-mcp = { path = "../../", editable = true }
|
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 }
|
arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true }
|
||||||
|
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
files = [ "arcade_postgres/**/*.py",]
|
files = [ "arcade_postgres/**/*.py",]
|
||||||
python_version = "3.10"
|
python_version = "3.10"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import os
|
import os
|
||||||
from os import environ
|
from os import environ
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
from arcade_mcp_server import Context
|
||||||
|
from arcade_mcp_server.exceptions import RetryableToolError
|
||||||
from arcade_postgres.tools.postgres import (
|
from arcade_postgres.tools.postgres import (
|
||||||
DatabaseEngine,
|
DatabaseEngine,
|
||||||
discover_schemas,
|
discover_schemas,
|
||||||
|
|
@ -10,8 +13,6 @@ from arcade_postgres.tools.postgres import (
|
||||||
execute_select_query,
|
execute_select_query,
|
||||||
get_table_schema,
|
get_table_schema,
|
||||||
)
|
)
|
||||||
from arcade_tdk import ToolContext, ToolSecretItem
|
|
||||||
from arcade_tdk.errors import RetryableToolError
|
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine
|
from sqlalchemy.ext.asyncio import create_async_engine
|
||||||
|
|
||||||
|
|
@ -23,14 +24,8 @@ POSTGRES_DATABASE_CONNECTION_STRING = (
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_context():
|
def mock_context():
|
||||||
context = ToolContext()
|
context = MagicMock(spec=Context)
|
||||||
context.secrets = []
|
context.get_secret = MagicMock(return_value=POSTGRES_DATABASE_CONNECTION_STRING)
|
||||||
context.secrets.append(
|
|
||||||
ToolSecretItem(
|
|
||||||
key="POSTGRES_DATABASE_CONNECTION_STRING", value=POSTGRES_DATABASE_CONNECTION_STRING
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return context
|
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
|
from typing import Annotated, Any
|
||||||
|
|
||||||
import httpx
|
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 (
|
from arcade_mcp_server.metadata import (
|
||||||
Behavior,
|
Behavior,
|
||||||
Classification,
|
Classification,
|
||||||
|
|
@ -9,9 +12,6 @@ from arcade_mcp_server.metadata import (
|
||||||
ServiceDomain,
|
ServiceDomain,
|
||||||
ToolMetadata,
|
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.enums import ArticleSortBy, SortOrder
|
||||||
from arcade_zendesk.utils import (
|
from arcade_zendesk.utils import (
|
||||||
|
|
@ -41,7 +41,7 @@ logger = logging.getLogger(__name__)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def search_articles(
|
async def search_articles(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
query: Annotated[
|
query: Annotated[
|
||||||
str | None,
|
str | None,
|
||||||
"Search text to match against articles. Supports quoted expressions for exact matching",
|
"Search text to match against articles. Supports quoted expressions for exact matching",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
from typing import Annotated, Any
|
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 (
|
from arcade_mcp_server.metadata import (
|
||||||
Behavior,
|
Behavior,
|
||||||
Classification,
|
Classification,
|
||||||
|
|
@ -7,8 +9,6 @@ from arcade_mcp_server.metadata import (
|
||||||
ServiceDomain,
|
ServiceDomain,
|
||||||
ToolMetadata,
|
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
|
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(
|
async def who_am_i(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
) -> Annotated[
|
) -> Annotated[
|
||||||
dict[str, Any],
|
dict[str, Any],
|
||||||
"Get comprehensive user profile and Zendesk account information.",
|
"Get comprehensive user profile and Zendesk account information.",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
from typing import Annotated, Any
|
from typing import Annotated, Any
|
||||||
|
|
||||||
import httpx
|
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 (
|
from arcade_mcp_server.metadata import (
|
||||||
Behavior,
|
Behavior,
|
||||||
Classification,
|
Classification,
|
||||||
|
|
@ -8,9 +11,6 @@ from arcade_mcp_server.metadata import (
|
||||||
ServiceDomain,
|
ServiceDomain,
|
||||||
ToolMetadata,
|
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.enums import SortOrder, TicketStatus
|
||||||
from arcade_zendesk.utils import fetch_paginated_results, get_zendesk_subdomain
|
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(
|
async def list_tickets(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
status: Annotated[
|
status: Annotated[
|
||||||
TicketStatus,
|
TicketStatus,
|
||||||
"The status of tickets to filter by. Defaults to 'open'",
|
"The status of tickets to filter by. Defaults to 'open'",
|
||||||
|
|
@ -166,7 +166,7 @@ async def list_tickets(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def get_ticket_comments(
|
async def get_ticket_comments(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
ticket_id: Annotated[int, "The ID of the ticket to get comments for"],
|
ticket_id: Annotated[int, "The ID of the ticket to get comments for"],
|
||||||
) -> Annotated[
|
) -> Annotated[
|
||||||
dict[str, Any], "A dictionary containing the ticket comments, metadata, and ticket URL"
|
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(
|
async def add_ticket_comment(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
ticket_id: Annotated[int, "The ID of the ticket to comment on"],
|
ticket_id: Annotated[int, "The ID of the ticket to comment on"],
|
||||||
comment_body: Annotated[str, "The text of the comment"],
|
comment_body: Annotated[str, "The text of the comment"],
|
||||||
public: Annotated[
|
public: Annotated[
|
||||||
|
|
@ -300,7 +300,7 @@ async def add_ticket_comment(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def mark_ticket_solved(
|
async def mark_ticket_solved(
|
||||||
context: ToolContext,
|
context: Context,
|
||||||
ticket_id: Annotated[int, "The ID of the ticket to mark as solved"],
|
ticket_id: Annotated[int, "The ID of the ticket to mark as solved"],
|
||||||
comment_body: Annotated[
|
comment_body: Annotated[
|
||||||
str | None,
|
str | None,
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import re
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from arcade_tdk import ToolContext
|
from arcade_mcp_server import Context
|
||||||
from arcade_tdk.errors import ToolExecutionError
|
from arcade_mcp_server.exceptions import ToolExecutionError
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -189,7 +189,7 @@ def validate_date_format(date_string: str) -> bool:
|
||||||
return False
|
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.
|
Get the Zendesk subdomain from secrets with proper error handling.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import Any, TypedDict
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from arcade_tdk import ToolContext
|
from arcade_mcp_server import Context
|
||||||
|
|
||||||
|
|
||||||
class WhoAmIResponse(TypedDict, total=False):
|
class WhoAmIResponse(TypedDict, total=False):
|
||||||
|
|
@ -19,7 +19,7 @@ class WhoAmIResponse(TypedDict, total=False):
|
||||||
zendesk_access: bool
|
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."""
|
"""Build comprehensive who am I response for Zendesk."""
|
||||||
user_info = await _get_current_user(context)
|
user_info = await _get_current_user(context)
|
||||||
organization_info = await _get_organization_info(context, user_info.get("organization_id"))
|
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]
|
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."""
|
"""Get current user information from Zendesk API."""
|
||||||
subdomain = context.get_secret("ZENDESK_SUBDOMAIN")
|
subdomain = context.get_secret("ZENDESK_SUBDOMAIN")
|
||||||
base_url = f"https://{subdomain}.zendesk.com"
|
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]
|
return response.json().get("user", {}) # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
||||||
async def _get_organization_info(
|
async def _get_organization_info(context: Context, organization_id: int | None) -> dict[str, Any]:
|
||||||
context: ToolContext, organization_id: int | None
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""Get organization information from Zendesk API."""
|
"""Get organization information from Zendesk API."""
|
||||||
if not organization_id:
|
if not organization_id:
|
||||||
return {}
|
return {}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from arcade_core import ToolCatalog
|
||||||
from arcade_evals import (
|
from arcade_evals import (
|
||||||
DatetimeCritic,
|
DatetimeCritic,
|
||||||
EvalRubric,
|
EvalRubric,
|
||||||
|
|
@ -8,7 +9,6 @@ from arcade_evals import (
|
||||||
tool_eval,
|
tool_eval,
|
||||||
)
|
)
|
||||||
from arcade_evals.critic import BinaryCritic, SimilarityCritic
|
from arcade_evals.critic import BinaryCritic, SimilarityCritic
|
||||||
from arcade_tdk import ToolCatalog
|
|
||||||
|
|
||||||
import arcade_zendesk
|
import arcade_zendesk
|
||||||
from arcade_zendesk.enums import ArticleSortBy, SortOrder
|
from arcade_zendesk.enums import ArticleSortBy, SortOrder
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from arcade_core import ToolCatalog
|
||||||
from arcade_evals import (
|
from arcade_evals import (
|
||||||
BinaryCritic,
|
BinaryCritic,
|
||||||
EvalRubric,
|
EvalRubric,
|
||||||
|
|
@ -6,7 +7,6 @@ from arcade_evals import (
|
||||||
SimilarityCritic,
|
SimilarityCritic,
|
||||||
tool_eval,
|
tool_eval,
|
||||||
)
|
)
|
||||||
from arcade_tdk import ToolCatalog
|
|
||||||
|
|
||||||
import arcade_zendesk
|
import arcade_zendesk
|
||||||
from arcade_zendesk.enums import SortOrder, TicketStatus
|
from arcade_zendesk.enums import SortOrder, TicketStatus
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,21 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "arcade_zendesk"
|
name = "arcade_zendesk"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arcade-tdk>=3.0.0,<4.0.0",
|
|
||||||
"arcade-mcp-server>=1.17.0,<2.0.0",
|
"arcade-mcp-server>=1.17.0,<2.0.0",
|
||||||
"httpx>=0.25.0,<1.0.0",
|
"httpx>=0.25.0,<1.0.0",
|
||||||
"beautifulsoup4>=4.0.0,<5"
|
"beautifulsoup4>=4.0.0,<5"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
arcade-zendesk = "arcade_zendesk.__main__:main"
|
||||||
|
arcade_zendesk = "arcade_zendesk.__main__:main"
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
"arcade-mcp[all]>=1.2.0,<2.0.0",
|
"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>=8.3.0,<8.4.0",
|
||||||
"pytest-cov>=4.0.0,<4.1.0",
|
"pytest-cov>=4.0.0,<4.1.0",
|
||||||
"pytest-mock>=3.11.1,<3.12.0",
|
"pytest-mock>=3.11.1,<3.12.0",
|
||||||
|
|
@ -31,8 +32,6 @@ dev = [
|
||||||
# Use local path sources for arcade libs when working locally
|
# Use local path sources for arcade libs when working locally
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
arcade-mcp = { path = "../../", editable = true }
|
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 }
|
arcade-mcp-server = { path = "../../libs/arcade-mcp-server/", editable = true }
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
from unittest.mock import AsyncMock, MagicMock
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from arcade_tdk import ToolContext
|
from arcade_mcp_server import Context
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_context():
|
def mock_context():
|
||||||
"""Standard mock context fixture used across all arcade toolkits."""
|
"""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_auth_token_or_empty = MagicMock(return_value="fake-token")
|
||||||
context.get_secret = MagicMock()
|
context.get_secret = MagicMock()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import pytest
|
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.enums import ArticleSortBy, SortOrder
|
||||||
from arcade_zendesk.tools.search_articles import search_articles
|
from arcade_zendesk.tools.search_articles import search_articles
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue