diff --git a/toolkits/brightdata/arcade_brightdata/tools/bright_data_tools.py b/toolkits/brightdata/arcade_brightdata/tools/bright_data_tools.py index a62d516c..ba6abace 100644 --- a/toolkits/brightdata/arcade_brightdata/tools/bright_data_tools.py +++ b/toolkits/brightdata/arcade_brightdata/tools/bright_data_tools.py @@ -5,6 +5,13 @@ from typing import Annotated, Any, cast import requests from arcade_core.errors import RetryableToolError +from arcade_mcp_server.metadata import ( + Behavior, + Classification, + Operation, + ServiceDomain, + ToolMetadata, +) from arcade_tdk import ToolContext, tool from arcade_brightdata.bright_data_client import BrightDataClient @@ -51,7 +58,21 @@ class SourceType(str, Enum): YOUTUBE_VIDEOS = "youtube_videos" -@tool(requires_secrets=["BRIGHTDATA_API_KEY", "BRIGHTDATA_ZONE"]) +@tool( + requires_secrets=["BRIGHTDATA_API_KEY", "BRIGHTDATA_ZONE"], + metadata=ToolMetadata( + classification=Classification( + service_domains=[ServiceDomain.WEB_SCRAPING], + ), + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) def scrape_as_markdown( context: ToolContext, url: Annotated[str, "URL to scrape"], @@ -71,7 +92,21 @@ def scrape_as_markdown( return client.make_request(payload) -@tool(requires_secrets=["BRIGHTDATA_API_KEY", "BRIGHTDATA_ZONE"]) +@tool( + requires_secrets=["BRIGHTDATA_API_KEY", "BRIGHTDATA_ZONE"], + metadata=ToolMetadata( + classification=Classification( + service_domains=[ServiceDomain.WEB_SCRAPING], + ), + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) def search_engine( # noqa: C901 context: ToolContext, query: Annotated[str, "Search query"], @@ -167,7 +202,21 @@ def search_engine( # noqa: C901 return client.make_request(payload) -@tool(requires_secrets=["BRIGHTDATA_API_KEY"]) +@tool( + requires_secrets=["BRIGHTDATA_API_KEY"], + metadata=ToolMetadata( + classification=Classification( + service_domains=[ServiceDomain.WEB_SCRAPING], + ), + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=False, + open_world=True, + ), + ), +) def web_data_feed( context: ToolContext, source_type: Annotated[SourceType, "Type of data source"], diff --git a/toolkits/brightdata/pyproject.toml b/toolkits/brightdata/pyproject.toml index 1180972e..a2341e46 100644 --- a/toolkits/brightdata/pyproject.toml +++ b/toolkits/brightdata/pyproject.toml @@ -4,11 +4,12 @@ build-backend = "hatchling.build" [project] name = "arcade_brightdata" -version = "0.2.0" +version = "0.3.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", ] [[project.authors]] @@ -49,6 +50,7 @@ ignore_missing_imports = "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 } [tool.pytest.ini_options] testpaths = [ "tests",] diff --git a/toolkits/clickhouse/arcade_clickhouse/tools/clickhouse.py b/toolkits/clickhouse/arcade_clickhouse/tools/clickhouse.py index cc5a039c..b1fd2cbe 100644 --- a/toolkits/clickhouse/arcade_clickhouse/tools/clickhouse.py +++ b/toolkits/clickhouse/arcade_clickhouse/tools/clickhouse.py @@ -1,12 +1,24 @@ from typing import Annotated, Any +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 -@tool(requires_secrets=["CLICKHOUSE_DATABASE_CONNECTION_STRING"]) +@tool( + requires_secrets=["CLICKHOUSE_DATABASE_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def discover_schemas( context: ToolContext, ) -> list[str]: @@ -18,7 +30,18 @@ async def discover_schemas( return ["default"] -@tool(requires_secrets=["CLICKHOUSE_DATABASE_CONNECTION_STRING"]) +@tool( + requires_secrets=["CLICKHOUSE_DATABASE_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def discover_databases( context: ToolContext, ) -> list[str]: @@ -30,7 +53,18 @@ async def discover_databases( return databases -@tool(requires_secrets=["CLICKHOUSE_DATABASE_CONNECTION_STRING"]) +@tool( + requires_secrets=["CLICKHOUSE_DATABASE_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def discover_tables( context: ToolContext, ) -> list[str]: @@ -45,7 +79,18 @@ async def discover_tables( return tables -@tool(requires_secrets=["CLICKHOUSE_DATABASE_CONNECTION_STRING"]) +@tool( + requires_secrets=["CLICKHOUSE_DATABASE_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def get_table_schema( context: ToolContext, schema_name: Annotated[str, "The schema to get the table schema of"], @@ -62,7 +107,18 @@ async def get_table_schema( return await _get_table_schema(client, "default", table_name) -@tool(requires_secrets=["CLICKHOUSE_DATABASE_CONNECTION_STRING"]) +@tool( + requires_secrets=["CLICKHOUSE_DATABASE_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def execute_select_query( context: ToolContext, select_clause: Annotated[ diff --git a/toolkits/clickhouse/pyproject.toml b/toolkits/clickhouse/pyproject.toml index 3884baf0..009d3511 100644 --- a/toolkits/clickhouse/pyproject.toml +++ b/toolkits/clickhouse/pyproject.toml @@ -4,11 +4,12 @@ build-backend = "hatchling.build" [project] name = "arcade_clickhouse" -version = "0.1.1" +version = "0.2.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", "sqlalchemy>=2.0.41", @@ -41,6 +42,7 @@ dev = [ 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] diff --git a/toolkits/linkedin/arcade_linkedin/tools/share.py b/toolkits/linkedin/arcade_linkedin/tools/share.py index 16549e0a..b804f84e 100644 --- a/toolkits/linkedin/arcade_linkedin/tools/share.py +++ b/toolkits/linkedin/arcade_linkedin/tools/share.py @@ -1,5 +1,12 @@ from typing import Annotated +from arcade_mcp_server.metadata import ( + Behavior, + Classification, + Operation, + ServiceDomain, + ToolMetadata, +) from arcade_tdk import ToolContext, tool from arcade_tdk.auth import LinkedIn from arcade_tdk.errors import ToolExecutionError @@ -10,7 +17,19 @@ from arcade_linkedin.tools.utils import _handle_linkedin_api_error, _send_linked @tool( requires_auth=LinkedIn( scopes=["w_member_social"], - ) + ), + metadata=ToolMetadata( + classification=Classification( + service_domains=[ServiceDomain.SOCIAL_MEDIA], + ), + behavior=Behavior( + operations=[Operation.CREATE], + read_only=False, + destructive=False, + idempotent=False, + open_world=True, + ), + ), ) async def create_text_post( context: ToolContext, diff --git a/toolkits/linkedin/pyproject.toml b/toolkits/linkedin/pyproject.toml index 38ca3be5..098fd92c 100644 --- a/toolkits/linkedin/pyproject.toml +++ b/toolkits/linkedin/pyproject.toml @@ -4,11 +4,12 @@ build-backend = "hatchling.build" [project] name = "arcade_linkedin" -version = "0.1.14" +version = "0.2.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", ] [[project.authors]] @@ -34,6 +35,7 @@ dev = [ 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] files = [ "arcade_linkedin/**/*.py",] diff --git a/toolkits/math/arcade_math/tools/__init__.py b/toolkits/math/arcade_math/tools/__init__.py index 4b3d12f8..bee1b80a 100644 --- a/toolkits/math/arcade_math/tools/__init__.py +++ b/toolkits/math/arcade_math/tools/__init__.py @@ -39,27 +39,27 @@ from arcade_math.tools.trigonometry import ( ) __all__ = [ - "add", - "subtract", - "multiply", - "divide", - "sum_list", - "sum_range", - "mod", - "log", - "power", "abs_val", + "add", + "avg", + "ceil", + "deg_to_rad", + "divide", "factorial", - "sqrt", + "floor", + "gcd", "generate_random_float", "generate_random_int", - "gcd", "lcm", - "ceil", - "floor", - "round_num", - "avg", + "log", "median", - "deg_to_rad", + "mod", + "multiply", + "power", "rad_to_deg", + "round_num", + "sqrt", + "subtract", + "sum_list", + "sum_range", ] diff --git a/toolkits/math/arcade_math/tools/arithmetic.py b/toolkits/math/arcade_math/tools/arithmetic.py index 251c09ff..b0029a1f 100644 --- a/toolkits/math/arcade_math/tools/arithmetic.py +++ b/toolkits/math/arcade_math/tools/arithmetic.py @@ -2,12 +2,22 @@ import decimal from decimal import Decimal from typing import Annotated +from arcade_mcp_server.metadata import Behavior, ToolMetadata from arcade_tdk import tool decimal.getcontext().prec = 100 -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def add( a: Annotated[str, "The first number as a string"], b: Annotated[str, "The second number as a string"], @@ -21,7 +31,16 @@ def add( return str(a_decimal + b_decimal) -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def subtract( a: Annotated[str, "The first number as a string"], b: Annotated[str, "The second number as a string"], @@ -35,7 +54,16 @@ def subtract( return str(a_decimal - b_decimal) -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def multiply( a: Annotated[str, "The first number as a string"], b: Annotated[str, "The second number as a string"], @@ -49,7 +77,16 @@ def multiply( return str(a_decimal * b_decimal) -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def divide( a: Annotated[str, "The first number as a string"], b: Annotated[str, "The second number as a string"], @@ -63,7 +100,16 @@ def divide( return str(a_decimal / b_decimal) -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def sum_list( numbers: Annotated[list[str], "The list of numbers as strings"], ) -> Annotated[str, "The sum of the numbers in the list as a string"]: @@ -74,7 +120,16 @@ def sum_list( return str(sum([Decimal(n) for n in numbers])) -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def sum_range( start: Annotated[str, "The start of the range to sum as a string"], end: Annotated[str, "The end of the range to sum as a string"], @@ -85,7 +140,16 @@ def sum_range( return str(sum(list(range(int(start), int(end) + 1)))) -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def mod( a: Annotated[str, "The dividend as a string"], b: Annotated[str, "The divisor as a string"], diff --git a/toolkits/math/arcade_math/tools/exponents.py b/toolkits/math/arcade_math/tools/exponents.py index 14fbd55e..9192e748 100644 --- a/toolkits/math/arcade_math/tools/exponents.py +++ b/toolkits/math/arcade_math/tools/exponents.py @@ -3,12 +3,22 @@ import math from decimal import Decimal from typing import Annotated +from arcade_mcp_server.metadata import Behavior, ToolMetadata from arcade_tdk import tool decimal.getcontext().prec = 100 -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def log( a: Annotated[str, "The number to take the logarithm of as a string"], base: Annotated[str, "The logarithmic base as a string"], @@ -20,7 +30,16 @@ def log( return str(math.log(Decimal(a), Decimal(base))) -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def power( a: Annotated[str, "The base number as a string"], b: Annotated[str, "The exponent as a string"], diff --git a/toolkits/math/arcade_math/tools/miscellaneous.py b/toolkits/math/arcade_math/tools/miscellaneous.py index db52e92c..cdba5322 100644 --- a/toolkits/math/arcade_math/tools/miscellaneous.py +++ b/toolkits/math/arcade_math/tools/miscellaneous.py @@ -3,12 +3,22 @@ import math from decimal import Decimal from typing import Annotated +from arcade_mcp_server.metadata import Behavior, ToolMetadata from arcade_tdk import tool decimal.getcontext().prec = 100 -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def abs_val( a: Annotated[str, "The number as a string"], ) -> Annotated[str, "The absolute value of the number as a string"]: @@ -19,7 +29,16 @@ def abs_val( return str(abs(Decimal(a))) -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def factorial( a: Annotated[str, "The non-negative integer to compute the factorial for as a string"], ) -> Annotated[str, "The factorial of the number as a string"]: @@ -30,7 +49,16 @@ def factorial( return str(math.factorial(int(a))) -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def sqrt( a: Annotated[str, "The number to square root as a string"], ) -> Annotated[str, "The square root of the number as a string"]: diff --git a/toolkits/math/arcade_math/tools/random.py b/toolkits/math/arcade_math/tools/random.py index 7b50d345..d12846c6 100644 --- a/toolkits/math/arcade_math/tools/random.py +++ b/toolkits/math/arcade_math/tools/random.py @@ -1,10 +1,20 @@ import random from typing import Annotated +from arcade_mcp_server.metadata import Behavior, ToolMetadata from arcade_tdk import tool -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=False, + open_world=False, + ), + ), +) def generate_random_int( min_value: Annotated[str, "The minimum value of the random integer as a string"], max_value: Annotated[str, "The maximum value of the random integer as a string"], @@ -21,7 +31,16 @@ def generate_random_int( return str(random.randint(int(min_value), int(max_value))) # noqa: S311 -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=False, + open_world=False, + ), + ), +) def generate_random_float( min_value: Annotated[str, "The minimum value of the random float as a string"], max_value: Annotated[str, "The maximum value of the random float as a string"], diff --git a/toolkits/math/arcade_math/tools/rational.py b/toolkits/math/arcade_math/tools/rational.py index fa314919..e76f2572 100644 --- a/toolkits/math/arcade_math/tools/rational.py +++ b/toolkits/math/arcade_math/tools/rational.py @@ -1,10 +1,20 @@ import math from typing import Annotated +from arcade_mcp_server.metadata import Behavior, ToolMetadata from arcade_tdk import tool -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def gcd( a: Annotated[str, "First integer as a string"], b: Annotated[str, "Second integer as a string"], @@ -15,7 +25,16 @@ def gcd( return str(math.gcd(int(a), int(b))) -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def lcm( a: Annotated[str, "First integer as a string"], b: Annotated[str, "Second integer as a string"], diff --git a/toolkits/math/arcade_math/tools/rounding.py b/toolkits/math/arcade_math/tools/rounding.py index 9d9e7ea0..1e6e04ba 100644 --- a/toolkits/math/arcade_math/tools/rounding.py +++ b/toolkits/math/arcade_math/tools/rounding.py @@ -3,12 +3,22 @@ import math from decimal import Decimal from typing import Annotated +from arcade_mcp_server.metadata import Behavior, ToolMetadata from arcade_tdk import tool decimal.getcontext().prec = 100 -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def ceil( a: Annotated[str, "The number to round up as a string"], ) -> Annotated[str, "The smallest integer greater than or equal to the number as a string"]: @@ -19,7 +29,16 @@ def ceil( return str(math.ceil(Decimal(a))) -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def floor( a: Annotated[str, "The number to round down as a string"], ) -> Annotated[str, "The largest integer less than or equal to the number as a string"]: @@ -30,7 +49,16 @@ def floor( return str(math.floor(Decimal(a))) -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def round_num( value: Annotated[str, "The number to round as a string"], ndigits: Annotated[str, "The number of digits after the decimal point as a string"], diff --git a/toolkits/math/arcade_math/tools/statistics.py b/toolkits/math/arcade_math/tools/statistics.py index 2001d6e0..1bc6c3fd 100644 --- a/toolkits/math/arcade_math/tools/statistics.py +++ b/toolkits/math/arcade_math/tools/statistics.py @@ -3,12 +3,22 @@ from decimal import Decimal from statistics import median as stats_median from typing import Annotated +from arcade_mcp_server.metadata import Behavior, ToolMetadata from arcade_tdk import tool decimal.getcontext().prec = 100 -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def avg( numbers: Annotated[list[str], "The list of numbers as strings"], ) -> Annotated[str, "The average (mean) of the numbers in the list as a string"]: @@ -21,7 +31,16 @@ def avg( return str(sum(d_numbers) / len(d_numbers)) if d_numbers else "0.0" -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def median( numbers: Annotated[list[str], "A list of numbers as strings"], ) -> Annotated[str, "The median value of the numbers in the list as a string"]: diff --git a/toolkits/math/arcade_math/tools/trigonometry.py b/toolkits/math/arcade_math/tools/trigonometry.py index db982439..a322f824 100644 --- a/toolkits/math/arcade_math/tools/trigonometry.py +++ b/toolkits/math/arcade_math/tools/trigonometry.py @@ -3,12 +3,22 @@ import math from decimal import Decimal from typing import Annotated +from arcade_mcp_server.metadata import Behavior, ToolMetadata from arcade_tdk import tool decimal.getcontext().prec = 100 -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def deg_to_rad( degrees: Annotated[str, "Angle in degrees as a string"], ) -> Annotated[str, "Angle in radians as a string"]: @@ -19,7 +29,16 @@ def deg_to_rad( return str(math.radians(Decimal(degrees))) -@tool +@tool( + metadata=ToolMetadata( + behavior=Behavior( + read_only=True, + destructive=False, + idempotent=True, + open_world=False, + ), + ), +) def rad_to_deg( radians: Annotated[str, "Angle in radians as a string"], ) -> Annotated[str, "Angle in degrees as a string"]: diff --git a/toolkits/math/pyproject.toml b/toolkits/math/pyproject.toml index 02c974d1..9b03cce1 100644 --- a/toolkits/math/pyproject.toml +++ b/toolkits/math/pyproject.toml @@ -4,11 +4,12 @@ build-backend = "hatchling.build" [project] name = "arcade_math" -version = "1.0.5" +version = "1.1.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]] name = "Arcade" @@ -33,6 +34,7 @@ dev = [ 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] files = [ "arcade_math/**/*.py",] diff --git a/toolkits/mongodb/arcade_mongodb/tools/mongodb.py b/toolkits/mongodb/arcade_mongodb/tools/mongodb.py index 68176862..317e09af 100644 --- a/toolkits/mongodb/arcade_mongodb/tools/mongodb.py +++ b/toolkits/mongodb/arcade_mongodb/tools/mongodb.py @@ -1,6 +1,7 @@ import json from typing import Annotated, Any +from arcade_mcp_server.metadata import Behavior, Operation, ToolMetadata from arcade_tdk import ToolContext, tool from arcade_tdk.errors import RetryableToolError @@ -20,7 +21,18 @@ from .utils import ( # BANNED = "banned" -@tool(requires_secrets=["MONGODB_CONNECTION_STRING"]) +@tool( + requires_secrets=["MONGODB_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def discover_databases( context: ToolContext, ) -> list[str]: @@ -32,7 +44,18 @@ async def discover_databases( return databases -@tool(requires_secrets=["MONGODB_CONNECTION_STRING"]) +@tool( + requires_secrets=["MONGODB_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def discover_collections( context: ToolContext, database_name: Annotated[str, "The database name to discover collections in"], @@ -48,7 +71,18 @@ async def discover_collections( return list(collections) -@tool(requires_secrets=["MONGODB_CONNECTION_STRING"]) +@tool( + requires_secrets=["MONGODB_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def get_collection_schema( context: ToolContext, database_name: Annotated[str, "The database name to get the collection schema of"], @@ -90,7 +124,18 @@ async def get_collection_schema( } -@tool(requires_secrets=["MONGODB_CONNECTION_STRING"]) +@tool( + requires_secrets=["MONGODB_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def find_documents( context: ToolContext, database_name: Annotated[str, "The database name to query"], @@ -193,7 +238,18 @@ async def find_documents( ) from e -@tool(requires_secrets=["MONGODB_CONNECTION_STRING"]) +@tool( + requires_secrets=["MONGODB_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def count_documents( context: ToolContext, database_name: Annotated[str, "The database name to query"], @@ -230,7 +286,18 @@ async def count_documents( ) from e -@tool(requires_secrets=["MONGODB_CONNECTION_STRING"]) +@tool( + requires_secrets=["MONGODB_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def aggregate_documents( context: ToolContext, database_name: Annotated[str, "The database name to query"], diff --git a/toolkits/mongodb/pyproject.toml b/toolkits/mongodb/pyproject.toml index eaf48ce7..65ba1b77 100644 --- a/toolkits/mongodb/pyproject.toml +++ b/toolkits/mongodb/pyproject.toml @@ -4,11 +4,12 @@ build-backend = "hatchling.build" [project] name = "arcade_mongodb" -version = "0.1.1" +version = "0.2.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", "motor>=3.6.0", @@ -37,6 +38,7 @@ dev = [ 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] diff --git a/toolkits/postgres/arcade_postgres/tools/postgres.py b/toolkits/postgres/arcade_postgres/tools/postgres.py index 003a23f1..01bc9812 100644 --- a/toolkits/postgres/arcade_postgres/tools/postgres.py +++ b/toolkits/postgres/arcade_postgres/tools/postgres.py @@ -1,5 +1,6 @@ from typing import Annotated, Any +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 @@ -8,7 +9,18 @@ from sqlalchemy.ext.asyncio import AsyncEngine from ..database_engine import MAX_ROWS_RETURNED, DatabaseEngine -@tool(requires_secrets=["POSTGRES_DATABASE_CONNECTION_STRING"]) +@tool( + requires_secrets=["POSTGRES_DATABASE_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def discover_schemas( context: ToolContext, ) -> list[str]: @@ -20,7 +32,18 @@ async def discover_schemas( return schemas -@tool(requires_secrets=["POSTGRES_DATABASE_CONNECTION_STRING"]) +@tool( + requires_secrets=["POSTGRES_DATABASE_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def discover_tables( context: ToolContext, schema_name: Annotated[ @@ -38,7 +61,18 @@ async def discover_tables( return tables -@tool(requires_secrets=["POSTGRES_DATABASE_CONNECTION_STRING"]) +@tool( + requires_secrets=["POSTGRES_DATABASE_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def get_table_schema( context: ToolContext, schema_name: Annotated[str, "The database schema to get the table schema of"], @@ -55,7 +89,18 @@ async def get_table_schema( return await _get_table_schema(engine, schema_name, table_name) -@tool(requires_secrets=["POSTGRES_DATABASE_CONNECTION_STRING"]) +@tool( + requires_secrets=["POSTGRES_DATABASE_CONNECTION_STRING"], + metadata=ToolMetadata( + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), +) async def execute_select_query( context: ToolContext, select_clause: Annotated[ diff --git a/toolkits/postgres/pyproject.toml b/toolkits/postgres/pyproject.toml index 1c150374..13dacb21 100644 --- a/toolkits/postgres/pyproject.toml +++ b/toolkits/postgres/pyproject.toml @@ -4,11 +4,12 @@ build-backend = "hatchling.build" [project] name = "arcade_postgres" -version = "0.3.1" +version = "0.4.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", "sqlalchemy>=2.0.41", @@ -40,6 +41,7 @@ dev = [ 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] diff --git a/toolkits/zendesk/arcade_zendesk/__init__.py b/toolkits/zendesk/arcade_zendesk/__init__.py index 373988de..16ac0662 100644 --- a/toolkits/zendesk/arcade_zendesk/__init__.py +++ b/toolkits/zendesk/arcade_zendesk/__init__.py @@ -7,9 +7,9 @@ from arcade_zendesk.tools import ( ) __all__ = [ - "list_tickets", "add_ticket_comment", "get_ticket_comments", + "list_tickets", "mark_ticket_solved", "search_articles", ] diff --git a/toolkits/zendesk/arcade_zendesk/tools/__init__.py b/toolkits/zendesk/arcade_zendesk/tools/__init__.py index 30cb0c3c..d2e90cbf 100644 --- a/toolkits/zendesk/arcade_zendesk/tools/__init__.py +++ b/toolkits/zendesk/arcade_zendesk/tools/__init__.py @@ -8,9 +8,9 @@ from arcade_zendesk.tools.tickets import ( ) __all__ = [ - "list_tickets", "add_ticket_comment", "get_ticket_comments", + "list_tickets", "mark_ticket_solved", "search_articles", "who_am_i", diff --git a/toolkits/zendesk/arcade_zendesk/tools/search_articles.py b/toolkits/zendesk/arcade_zendesk/tools/search_articles.py index d07dd437..4b4ac314 100644 --- a/toolkits/zendesk/arcade_zendesk/tools/search_articles.py +++ b/toolkits/zendesk/arcade_zendesk/tools/search_articles.py @@ -2,6 +2,13 @@ import logging from typing import Annotated, Any import httpx +from arcade_mcp_server.metadata import ( + Behavior, + Classification, + Operation, + ServiceDomain, + ToolMetadata, +) from arcade_tdk import ToolContext, tool from arcade_tdk.auth import OAuth2 from arcade_tdk.errors import RetryableToolError @@ -20,6 +27,18 @@ logger = logging.getLogger(__name__) @tool( requires_auth=OAuth2(id="zendesk", scopes=["read"]), requires_secrets=["ZENDESK_SUBDOMAIN"], + metadata=ToolMetadata( + classification=Classification( + service_domains=[ServiceDomain.CUSTOMER_SUPPORT], + ), + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), ) async def search_articles( context: ToolContext, diff --git a/toolkits/zendesk/arcade_zendesk/tools/system_context.py b/toolkits/zendesk/arcade_zendesk/tools/system_context.py index 643718ab..fc9b08f7 100644 --- a/toolkits/zendesk/arcade_zendesk/tools/system_context.py +++ b/toolkits/zendesk/arcade_zendesk/tools/system_context.py @@ -1,5 +1,12 @@ from typing import Annotated, Any +from arcade_mcp_server.metadata import ( + Behavior, + Classification, + Operation, + ServiceDomain, + ToolMetadata, +) from arcade_tdk import ToolContext, tool from arcade_tdk.auth import OAuth2 @@ -9,6 +16,18 @@ from arcade_zendesk.who_am_i_util import build_who_am_i_response @tool( requires_auth=OAuth2(id="zendesk", scopes=["read"]), requires_secrets=["ZENDESK_SUBDOMAIN"], + metadata=ToolMetadata( + classification=Classification( + service_domains=[ServiceDomain.CUSTOMER_SUPPORT], + ), + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), ) async def who_am_i( context: ToolContext, diff --git a/toolkits/zendesk/arcade_zendesk/tools/tickets.py b/toolkits/zendesk/arcade_zendesk/tools/tickets.py index 84e7ab6e..d18514f4 100644 --- a/toolkits/zendesk/arcade_zendesk/tools/tickets.py +++ b/toolkits/zendesk/arcade_zendesk/tools/tickets.py @@ -1,6 +1,13 @@ from typing import Annotated, Any import httpx +from arcade_mcp_server.metadata import ( + Behavior, + Classification, + Operation, + ServiceDomain, + ToolMetadata, +) from arcade_tdk import ToolContext, tool from arcade_tdk.auth import OAuth2 from arcade_tdk.errors import RetryableToolError @@ -23,6 +30,18 @@ def _handle_ticket_not_found(response: httpx.Response, ticket_id: int) -> None: @tool( requires_auth=OAuth2(id="zendesk", scopes=["read"]), requires_secrets=["ZENDESK_SUBDOMAIN"], + metadata=ToolMetadata( + classification=Classification( + service_domains=[ServiceDomain.CUSTOMER_SUPPORT], + ), + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), ) async def list_tickets( context: ToolContext, @@ -133,6 +152,18 @@ async def list_tickets( @tool( requires_auth=OAuth2(id="zendesk", scopes=["read"]), requires_secrets=["ZENDESK_SUBDOMAIN"], + metadata=ToolMetadata( + classification=Classification( + service_domains=[ServiceDomain.CUSTOMER_SUPPORT], + ), + behavior=Behavior( + operations=[Operation.READ], + read_only=True, + destructive=False, + idempotent=True, + open_world=True, + ), + ), ) async def get_ticket_comments( context: ToolContext, @@ -184,6 +215,18 @@ async def get_ticket_comments( @tool( requires_auth=OAuth2(id="zendesk", scopes=["tickets:write"]), requires_secrets=["ZENDESK_SUBDOMAIN"], + metadata=ToolMetadata( + classification=Classification( + service_domains=[ServiceDomain.CUSTOMER_SUPPORT], + ), + behavior=Behavior( + operations=[Operation.CREATE], + read_only=False, + destructive=False, + idempotent=False, + open_world=True, + ), + ), ) async def add_ticket_comment( context: ToolContext, @@ -243,6 +286,18 @@ async def add_ticket_comment( @tool( requires_auth=OAuth2(id="zendesk", scopes=["tickets:write"]), requires_secrets=["ZENDESK_SUBDOMAIN"], + metadata=ToolMetadata( + classification=Classification( + service_domains=[ServiceDomain.CUSTOMER_SUPPORT], + ), + behavior=Behavior( + operations=[Operation.UPDATE, Operation.CREATE], + read_only=False, + destructive=False, + idempotent=False, + open_world=True, + ), + ), ) async def mark_ticket_solved( context: ToolContext, diff --git a/toolkits/zendesk/pyproject.toml b/toolkits/zendesk/pyproject.toml index 5ff9e43c..57519054 100644 --- a/toolkits/zendesk/pyproject.toml +++ b/toolkits/zendesk/pyproject.toml @@ -4,10 +4,11 @@ build-backend = "hatchling.build" [project] name = "arcade_zendesk" -version = "0.3.1" +version = "0.4.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" ] @@ -32,6 +33,7 @@ dev = [ 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]