Adjust arcade docs command to the new docs repo structure (#592)
- Updates the `arcade docs` templates, dir/file paths, and URL paths to reflect the new docs repo structure - References "MCP Server" instead of "toolkit" - Auto-detects when it's a Starter MCP server and adds the corresponding warning in the main doc page - Fixes a bug that generated the wrong file path to the Python & JS examples when the package name had an underscore character) - Introduces some minor improvements, such as pulling the MCP Server description for `ToolInfo` from the package `pyproject.toml`, instead of a standard description varying only the MCP Server name --------- Co-authored-by: Eric Gustin <34000337+EricGustin@users.noreply.github.com>
This commit is contained in:
parent
56507d7112
commit
7dd62fcc89
6 changed files with 220 additions and 72 deletions
|
|
@ -7,11 +7,12 @@ from arcade_cli.toolkit_docs.docs_builder import (
|
||||||
build_example_path,
|
build_example_path,
|
||||||
build_examples,
|
build_examples,
|
||||||
build_toolkit_mdx,
|
build_toolkit_mdx,
|
||||||
build_toolkit_mdx_path,
|
build_toolkit_mdx_file_path,
|
||||||
)
|
)
|
||||||
from arcade_cli.toolkit_docs.utils import (
|
from arcade_cli.toolkit_docs.utils import (
|
||||||
get_all_enumerations,
|
get_all_enumerations,
|
||||||
get_list_of_tools,
|
get_list_of_tools,
|
||||||
|
has_wrapper_tools_directory,
|
||||||
print_debug_func,
|
print_debug_func,
|
||||||
read_toolkit_metadata,
|
read_toolkit_metadata,
|
||||||
resolve_api_key,
|
resolve_api_key,
|
||||||
|
|
@ -44,8 +45,9 @@ def generate_toolkit_docs(
|
||||||
|
|
||||||
docs_dir = standardize_dir_path(docs_dir)
|
docs_dir = standardize_dir_path(docs_dir)
|
||||||
toolkit_dir = standardize_dir_path(toolkit_dir)
|
toolkit_dir = standardize_dir_path(toolkit_dir)
|
||||||
|
is_wrapper_toolkit = has_wrapper_tools_directory(toolkit_dir)
|
||||||
|
|
||||||
print_debug("Reading toolkit metadata")
|
print_debug("Reading server metadata")
|
||||||
pip_package_name = read_toolkit_metadata(toolkit_dir)
|
pip_package_name = read_toolkit_metadata(toolkit_dir)
|
||||||
|
|
||||||
print_debug(f"Getting list of tools for {toolkit_name} from the local Python environment")
|
print_debug(f"Getting list of tools for {toolkit_name} from the local Python environment")
|
||||||
|
|
@ -56,16 +58,19 @@ def generate_toolkit_docs(
|
||||||
print_debug("Getting all enumerations potentially used in tool argument specs")
|
print_debug("Getting all enumerations potentially used in tool argument specs")
|
||||||
enums = get_all_enumerations(toolkit_dir)
|
enums = get_all_enumerations(toolkit_dir)
|
||||||
|
|
||||||
print_debug(f"Building /{toolkit_name.lower()}.mdx file")
|
toolkit_mdx_file_path = build_toolkit_mdx_file_path(docs_section, docs_dir, toolkit_name)
|
||||||
|
print_debug(f"Building {toolkit_mdx_file_path} file")
|
||||||
toolkit_mdx = build_toolkit_mdx(
|
toolkit_mdx = build_toolkit_mdx(
|
||||||
|
toolkit_package_name=toolkit_name,
|
||||||
|
toolkit_dir=toolkit_dir,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
docs_section=docs_section,
|
docs_section=docs_section,
|
||||||
enums=enums,
|
enums=enums,
|
||||||
pip_package_name=pip_package_name,
|
pip_package_name=pip_package_name,
|
||||||
openai_model=openai_model,
|
openai_model=openai_model,
|
||||||
|
is_wrapper_toolkit=is_wrapper_toolkit,
|
||||||
)
|
)
|
||||||
toolkit_mdx_path = build_toolkit_mdx_path(docs_section, docs_dir, toolkit_name)
|
write_file(toolkit_mdx_file_path, toolkit_mdx)
|
||||||
write_file(toolkit_mdx_path, toolkit_mdx)
|
|
||||||
|
|
||||||
if tool_call_examples:
|
if tool_call_examples:
|
||||||
print_debug("Building tool-call examples in Python and JavaScript")
|
print_debug("Building tool-call examples in Python and JavaScript")
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ from arcade_cli.toolkit_docs.templates import (
|
||||||
ENUM_MDX,
|
ENUM_MDX,
|
||||||
ENUM_VALUE,
|
ENUM_VALUE,
|
||||||
GENERIC_PROVIDER_CONFIG,
|
GENERIC_PROVIDER_CONFIG,
|
||||||
|
STARTER_TOOL_INFO_CALL,
|
||||||
|
STARTER_TOOLKIT_HEADER_IMPORT,
|
||||||
TABBED_EXAMPLES_LIST,
|
TABBED_EXAMPLES_LIST,
|
||||||
TABLE_OF_CONTENTS,
|
TABLE_OF_CONTENTS,
|
||||||
TABLE_OF_CONTENTS_ITEM,
|
TABLE_OF_CONTENTS_ITEM,
|
||||||
|
|
@ -36,6 +38,8 @@ from arcade_cli.toolkit_docs.templates import (
|
||||||
from arcade_cli.toolkit_docs.utils import (
|
from arcade_cli.toolkit_docs.utils import (
|
||||||
clean_fully_qualified_name,
|
clean_fully_qualified_name,
|
||||||
find_enum_by_options,
|
find_enum_by_options,
|
||||||
|
find_pyproject_toml,
|
||||||
|
get_pyproject_description,
|
||||||
get_toolkit_auth_type,
|
get_toolkit_auth_type,
|
||||||
is_well_known_provider,
|
is_well_known_provider,
|
||||||
pascal_to_snake_case,
|
pascal_to_snake_case,
|
||||||
|
|
@ -44,15 +48,30 @@ from arcade_cli.toolkit_docs.utils import (
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
def build_toolkit_mdx_path(docs_section: str, docs_root_dir: str, toolkit_name: str) -> str:
|
def build_toolkit_mdx_dir_path(
|
||||||
return os.path.join(
|
docs_section: str,
|
||||||
|
docs_root_dir: str,
|
||||||
|
toolkit_name: str,
|
||||||
|
ensure_exists: bool = True,
|
||||||
|
) -> str:
|
||||||
|
dir_path = os.path.join(
|
||||||
docs_root_dir,
|
docs_root_dir,
|
||||||
"pages",
|
"app",
|
||||||
"toolkits",
|
"en",
|
||||||
|
"mcp-servers",
|
||||||
docs_section,
|
docs_section,
|
||||||
f"{toolkit_name.lower()}.mdx",
|
f"{toolkit_name.lower().replace('_', '-')}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if ensure_exists:
|
||||||
|
os.makedirs(dir_path, exist_ok=True)
|
||||||
|
return dir_path
|
||||||
|
|
||||||
|
|
||||||
|
def build_toolkit_mdx_file_path(docs_section: str, docs_root_dir: str, toolkit_name: str) -> str:
|
||||||
|
toolkit_dir_path = build_toolkit_mdx_dir_path(docs_section, docs_root_dir, toolkit_name)
|
||||||
|
return os.path.join(toolkit_dir_path, "page.mdx")
|
||||||
|
|
||||||
|
|
||||||
def build_example_path(example_filename: str, docs_root_dir: str, toolkit_name: str) -> str:
|
def build_example_path(example_filename: str, docs_root_dir: str, toolkit_name: str) -> str:
|
||||||
return os.path.join(
|
return os.path.join(
|
||||||
|
|
@ -60,13 +79,15 @@ def build_example_path(example_filename: str, docs_root_dir: str, toolkit_name:
|
||||||
"public",
|
"public",
|
||||||
"examples",
|
"examples",
|
||||||
"integrations",
|
"integrations",
|
||||||
"toolkits",
|
"mcp-servers",
|
||||||
toolkit_name.lower(),
|
toolkit_name.lower(),
|
||||||
example_filename,
|
example_filename,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_toolkit_mdx(
|
def build_toolkit_mdx(
|
||||||
|
toolkit_package_name: str,
|
||||||
|
toolkit_dir: str,
|
||||||
tools: list[ToolDefinition],
|
tools: list[ToolDefinition],
|
||||||
docs_section: str,
|
docs_section: str,
|
||||||
enums: dict[str, type[Enum]],
|
enums: dict[str, type[Enum]],
|
||||||
|
|
@ -74,14 +95,32 @@ def build_toolkit_mdx(
|
||||||
openai_model: str,
|
openai_model: str,
|
||||||
toolkit_header_template: str = TOOLKIT_HEADER,
|
toolkit_header_template: str = TOOLKIT_HEADER,
|
||||||
toolkit_page_template: str = TOOLKIT_PAGE,
|
toolkit_page_template: str = TOOLKIT_PAGE,
|
||||||
|
is_wrapper_toolkit: bool = False,
|
||||||
) -> tuple[str, str]:
|
) -> tuple[str, str]:
|
||||||
sample_tool = tools[0]
|
sample_tool = tools[0]
|
||||||
toolkit_name = sample_tool.toolkit.name
|
toolkit_name = sample_tool.toolkit.name
|
||||||
toolkit_version = sample_tool.toolkit.version
|
toolkit_version = sample_tool.toolkit.version
|
||||||
auth_type = get_toolkit_auth_type(sample_tool.requirements.authorization)
|
auth_type = get_toolkit_auth_type(sample_tool.requirements)
|
||||||
|
|
||||||
|
if is_wrapper_toolkit:
|
||||||
|
starter_tool_info_import = STARTER_TOOLKIT_HEADER_IMPORT
|
||||||
|
starter_tool_info_warning = STARTER_TOOL_INFO_CALL.format(toolkit_name=toolkit_name)
|
||||||
|
else:
|
||||||
|
starter_tool_info_import = ""
|
||||||
|
starter_tool_info_warning = ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
pyproject_path = find_pyproject_toml(toolkit_dir)
|
||||||
|
tool_info_description = get_pyproject_description(pyproject_path)
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
tool_info_description = f"Enable Agents to interact with the {toolkit_name} MCP Server"
|
||||||
|
|
||||||
header = toolkit_header_template.format(
|
header = toolkit_header_template.format(
|
||||||
toolkit_title=toolkit_name,
|
toolkit_title=toolkit_name,
|
||||||
|
tool_info_description=tool_info_description,
|
||||||
|
starter_tool_info_import=starter_tool_info_import,
|
||||||
|
starter_tool_info_warning=starter_tool_info_warning,
|
||||||
description=generate_toolkit_description(
|
description=generate_toolkit_description(
|
||||||
toolkit_name,
|
toolkit_name,
|
||||||
[(tool.name, tool.description) for tool in tools],
|
[(tool.name, tool.description) for tool in tools],
|
||||||
|
|
@ -94,7 +133,9 @@ def build_toolkit_mdx(
|
||||||
table_of_contents = build_table_of_contents(tools)
|
table_of_contents = build_table_of_contents(tools)
|
||||||
footer = build_footer(toolkit_name, pip_package_name, sample_tool.requirements.authorization)
|
footer = build_footer(toolkit_name, pip_package_name, sample_tool.requirements.authorization)
|
||||||
|
|
||||||
referenced_enums, tools_specs = build_tools_specs(tools, docs_section, enums)
|
referenced_enums, tools_specs = build_tools_specs(
|
||||||
|
toolkit_package_name, tools, docs_section, enums
|
||||||
|
)
|
||||||
reference_mdx = build_reference_mdx(toolkit_name, referenced_enums) if referenced_enums else ""
|
reference_mdx = build_reference_mdx(toolkit_name, referenced_enums) if referenced_enums else ""
|
||||||
|
|
||||||
toolkit_mdx = toolkit_page_template.format(
|
toolkit_mdx = toolkit_page_template.format(
|
||||||
|
|
@ -201,6 +242,7 @@ def build_footer(
|
||||||
|
|
||||||
|
|
||||||
def build_tools_specs(
|
def build_tools_specs(
|
||||||
|
toolkit_name: str,
|
||||||
tools: list[ToolDefinition],
|
tools: list[ToolDefinition],
|
||||||
docs_section: str,
|
docs_section: str,
|
||||||
enums: dict[str, type[Enum]],
|
enums: dict[str, type[Enum]],
|
||||||
|
|
@ -212,6 +254,7 @@ def build_tools_specs(
|
||||||
referenced_enums = []
|
referenced_enums = []
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
tool_referenced_enums, tool_spec = build_tool_spec(
|
tool_referenced_enums, tool_spec = build_tool_spec(
|
||||||
|
toolkit_name=toolkit_name,
|
||||||
tool=tool,
|
tool=tool,
|
||||||
docs_section=docs_section,
|
docs_section=docs_section,
|
||||||
enums=enums,
|
enums=enums,
|
||||||
|
|
@ -226,6 +269,7 @@ def build_tools_specs(
|
||||||
|
|
||||||
|
|
||||||
def build_tool_spec(
|
def build_tool_spec(
|
||||||
|
toolkit_name: str,
|
||||||
tool: ToolDefinition,
|
tool: ToolDefinition,
|
||||||
docs_section: str,
|
docs_section: str,
|
||||||
enums: dict[str, type[Enum]],
|
enums: dict[str, type[Enum]],
|
||||||
|
|
@ -234,7 +278,7 @@ def build_tool_spec(
|
||||||
tool_spec_secrets_template: str = TOOL_SPEC_SECRETS,
|
tool_spec_secrets_template: str = TOOL_SPEC_SECRETS,
|
||||||
) -> tuple[list[tuple[str, type[Enum]]], str]:
|
) -> tuple[list[tuple[str, type[Enum]]], str]:
|
||||||
tabbed_examples_list = TABBED_EXAMPLES_LIST.format(
|
tabbed_examples_list = TABBED_EXAMPLES_LIST.format(
|
||||||
toolkit_name=tool.toolkit.name.lower(),
|
toolkit_name=toolkit_name.lower(),
|
||||||
tool_name=pascal_to_snake_case(tool.name),
|
tool_name=pascal_to_snake_case(tool.name),
|
||||||
)
|
)
|
||||||
referenced_enums, parameters = build_tool_parameters(
|
referenced_enums, parameters = build_tool_parameters(
|
||||||
|
|
@ -290,7 +334,7 @@ def build_tool_parameters(
|
||||||
if schema.enum:
|
if schema.enum:
|
||||||
enum_name, enum_class = find_enum_by_options(enums, schema.enum)
|
enum_name, enum_class = find_enum_by_options(enums, schema.enum)
|
||||||
referenced_enums.append((enum_name, enum_class))
|
referenced_enums.append((enum_name, enum_class))
|
||||||
param_definition = f"`Enum` [{enum_name}](/toolkits/{docs_section}/{toolkit_name}/reference#{enum_name})"
|
param_definition = f"`Enum` [{enum_name}](/mcp-servers/{docs_section}/{toolkit_name}/reference#{enum_name})"
|
||||||
else:
|
else:
|
||||||
if schema.inner_val_type:
|
if schema.inner_val_type:
|
||||||
param_definition = f"`{schema.val_type}[{schema.inner_val_type}]`"
|
param_definition = f"`{schema.val_type}[{schema.inner_val_type}]`"
|
||||||
|
|
@ -325,12 +369,15 @@ def build_examples(
|
||||||
interface_signature = build_tool_interface_signature(tool)
|
interface_signature = build_tool_interface_signature(tool)
|
||||||
input_map = generate_tool_input_map(interface_signature, openai_model)
|
input_map = generate_tool_input_map(interface_signature, openai_model)
|
||||||
fully_qualified_name = tool.fully_qualified_name.split("@")[0]
|
fully_qualified_name = tool.fully_qualified_name.split("@")[0]
|
||||||
|
|
||||||
|
py_file_name = f"{pascal_to_snake_case(tool.name)}_example_call_tool.py"
|
||||||
examples.append((
|
examples.append((
|
||||||
f"{pascal_to_snake_case(tool.name)}_example_call_tool.py",
|
py_file_name,
|
||||||
build_python_example(fully_qualified_name, input_map),
|
build_python_example(fully_qualified_name, input_map),
|
||||||
))
|
))
|
||||||
|
js_file_name = f"{pascal_to_snake_case(tool.name)}_example_call_tool.js"
|
||||||
examples.append((
|
examples.append((
|
||||||
f"{pascal_to_snake_case(tool.name)}_example_call_tool.js",
|
js_file_name,
|
||||||
build_javascript_example(fully_qualified_name, input_map),
|
build_javascript_example(fully_qualified_name, input_map),
|
||||||
))
|
))
|
||||||
return examples
|
return examples
|
||||||
|
|
@ -376,18 +423,18 @@ def generate_toolkit_description(
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": (
|
"content": (
|
||||||
"You are a helpful assistant. "
|
"You are a helpful assistant. "
|
||||||
"When given a toolkit name and a list of tools, you will generate a "
|
"When given an MCP Server name and a list of tools, you will generate a "
|
||||||
"short, yet descriptive of the toolkit and the main actions a user "
|
"short, yet descriptive of the MCP Server and the main actions a user "
|
||||||
"or LLM can perform with it.\n\n"
|
"or LLM can perform with it.\n\n"
|
||||||
"As an example, here is the Asana toolkit description:\n\n"
|
"As an example, here is the Asana MCP Server description:\n\n"
|
||||||
"The Arcade Asana toolkit provides a pre-built set of tools for "
|
"The Arcade Asana MCP Server provides a pre-built set of tools for "
|
||||||
"interacting with Asana. These tools make it easy to build agents "
|
"interacting with Asana. These tools make it easy to build agents "
|
||||||
"and AI apps that can:\n\n"
|
"and AI apps that can:\n\n"
|
||||||
"- Manage teams, projects, and workspaces.\n"
|
"- Manage teams, projects, and workspaces.\n"
|
||||||
"- Create, update, and search for tasks.\n"
|
"- Create, update, and search for tasks.\n"
|
||||||
"- Retrieve data about tasks, projects, workspaces, users, etc.\n"
|
"- Retrieve data about tasks, projects, workspaces, users, etc.\n"
|
||||||
"- Manage task attachments.\n\n"
|
"- Manage task attachments.\n\n"
|
||||||
"And here is a JSON string with the list of tools in the Asana toolkit:\n\n"
|
"And here is a JSON string with the list of tools in the Asana MCP Server:\n\n"
|
||||||
"```json\n\n"
|
"```json\n\n"
|
||||||
'[["AttachFileToTask", "Attaches a file to an Asana task\n\nProvide exactly '
|
'[["AttachFileToTask", "Attaches a file to an Asana task\n\nProvide exactly '
|
||||||
"one of file_content_str, file_content_base64, or file_content_url, never "
|
"one of file_content_str, file_content_base64, or file_content_url, never "
|
||||||
|
|
@ -412,18 +459,18 @@ def generate_toolkit_description(
|
||||||
'authenticated user"], ["MarkTaskAsCompleted", "Mark a task in Asana as '
|
'authenticated user"], ["MarkTaskAsCompleted", "Mark a task in Asana as '
|
||||||
'completed"], ["UpdateTask", "Updates a task in Asana"]]\n\n```\n\n'
|
'completed"], ["UpdateTask", "Updates a task in Asana"]]\n\n```\n\n'
|
||||||
"Keep the description concise and to the point. The user will provide you with "
|
"Keep the description concise and to the point. The user will provide you with "
|
||||||
"the toolkit name and the list of tools. Generate the description according to "
|
"the MCP Server name and the list of tools. Generate the description according to "
|
||||||
"the instructions above."
|
"the instructions above."
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": (
|
"content": (
|
||||||
f"The toolkit name is {toolkit_name} and the list of tools is:\n\n"
|
f"The MCP Server name is {toolkit_name} and the list of tools is:\n\n"
|
||||||
"```json\n\n"
|
"```json\n\n"
|
||||||
f"{json.dumps(tools, ensure_ascii=False)}\n\n"
|
f"{json.dumps(tools, ensure_ascii=False)}\n\n"
|
||||||
"```\n\n"
|
"```\n\n"
|
||||||
"Please generate a description for the toolkit."
|
"Please generate a description for the MCP Server."
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,21 @@ TOOLKIT_PAGE = """{header}
|
||||||
{footer}
|
{footer}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TOOLKIT_HEADER = """# {toolkit_title}
|
STARTER_TOOLKIT_HEADER_IMPORT = 'import StarterToolInfo from "@/app/_components/starter-tool-info";'
|
||||||
|
|
||||||
import ToolInfo from "@/components/ToolInfo";
|
STARTER_TOOL_INFO_CALL = '<StarterToolInfo toolkitName="{toolkit_name}" />'
|
||||||
import Badges from "@/components/Badges";
|
|
||||||
import TabbedCodeBlock from "@/components/TabbedCodeBlock";
|
TOOLKIT_HEADER = """# {toolkit_title}
|
||||||
import TableOfContents from "@/components/TableOfContents";
|
{starter_tool_info_import}
|
||||||
import ToolFooter from "@/components/ToolFooter";
|
import ToolInfo from "@/app/_components/tool-info";
|
||||||
|
import Badges from "@/app/_components/badges";
|
||||||
|
import TabbedCodeBlock from "@/app/_components/tabbed-code-block";
|
||||||
|
import TableOfContents from "@/app/_components/table-of-contents";
|
||||||
|
import ToolFooter from "@/app/_components/tool-footer";
|
||||||
|
import {{ Callout }} from "nextra/components";
|
||||||
|
|
||||||
<ToolInfo
|
<ToolInfo
|
||||||
description="Enable agents to interact with {toolkit_title}"
|
description="{tool_info_description}"
|
||||||
author="Arcade"
|
author="Arcade"
|
||||||
{auth_type}
|
{auth_type}
|
||||||
versions={{["{version}"]}}
|
versions={{["{version}"]}}
|
||||||
|
|
@ -24,6 +29,8 @@ import ToolFooter from "@/components/ToolFooter";
|
||||||
|
|
||||||
<Badges repo="arcadeai/{pip_package_name}" />
|
<Badges repo="arcadeai/{pip_package_name}" />
|
||||||
|
|
||||||
|
{starter_tool_info_warning}
|
||||||
|
|
||||||
{description}"""
|
{description}"""
|
||||||
|
|
||||||
TABLE_OF_CONTENTS = """## Available Tools
|
TABLE_OF_CONTENTS = """## Available Tools
|
||||||
|
|
@ -36,11 +43,11 @@ TABLE_OF_CONTENTS = """## Available Tools
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Tip>
|
<Callout>
|
||||||
If you need to perform an action that's not listed here, you can [get in touch
|
If you need to perform an action that's not listed here, you can [get in touch
|
||||||
with us](mailto:contact@arcade.dev) to request a new tool, or [create your
|
with us](mailto:contact@arcade.dev) to request a new tool, or [create your
|
||||||
own tools](/home/build-tools/create-a-toolkit).
|
own tools](/home/build-tools/create-a-mcp-server).
|
||||||
</Tip>"""
|
</Callout>"""
|
||||||
|
|
||||||
TABLE_OF_CONTENTS_ITEM = '\n ["{tool_fully_qualified_name}", "{description}"],'
|
TABLE_OF_CONTENTS_ITEM = '\n ["{tool_fully_qualified_name}", "{description}"],'
|
||||||
|
|
||||||
|
|
@ -59,7 +66,7 @@ TOOL_SPEC = """## {tool_fully_qualified_name}
|
||||||
|
|
||||||
TOOL_SPEC_SECRETS = """**Secrets**
|
TOOL_SPEC_SECRETS = """**Secrets**
|
||||||
|
|
||||||
This tool requires the following secrets: {secrets} (learn how to [configure secrets](/home/build-tools/create-a-tool-with-secrets#supplying-the-secret))
|
This tool requires the following secrets: {secrets} (learn how to [configure secrets](/home/build-tools/create-a-tool-with-secrets#set-the-secret-in-the-arcade-dashboard))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TABBED_EXAMPLES_LIST = """<TabbedCodeBlock
|
TABBED_EXAMPLES_LIST = """<TabbedCodeBlock
|
||||||
|
|
@ -67,8 +74,8 @@ TABBED_EXAMPLES_LIST = """<TabbedCodeBlock
|
||||||
{{
|
{{
|
||||||
label: "Call the Tool Directly",
|
label: "Call the Tool Directly",
|
||||||
content: {{
|
content: {{
|
||||||
Python: ["/examples/integrations/toolkits/{toolkit_name}/{tool_name}_example_call_tool.py"],
|
Python: ["/examples/integrations/mcp-servers/{toolkit_name}/{tool_name}_example_call_tool.py"],
|
||||||
JavaScript: ["/examples/integrations/toolkits/{toolkit_name}/{tool_name}_example_call_tool.js"],
|
JavaScript: ["/examples/integrations/mcp-servers/{toolkit_name}/{tool_name}_example_call_tool.js"],
|
||||||
}},
|
}},
|
||||||
}},
|
}},
|
||||||
]}}
|
]}}
|
||||||
|
|
@ -85,9 +92,9 @@ TOOLKIT_FOOTER_OAUTH2 = """## Auth
|
||||||
<ToolFooter pipPackageName="{pip_package_name}" />
|
<ToolFooter pipPackageName="{pip_package_name}" />
|
||||||
"""
|
"""
|
||||||
|
|
||||||
WELL_KNOWN_PROVIDER_CONFIG = "The Arcade {toolkit_name} toolkit uses the [{provider_name} auth provider](/home/auth-providers/{provider_id}) to connect to users' {toolkit_name} accounts. Please refer to the [{provider_name} auth provider](/home/auth-providers/{provider_id}) documentation to learn how to configure auth."
|
WELL_KNOWN_PROVIDER_CONFIG = "The Arcade {toolkit_name} MCP Server uses the [{provider_name} auth provider](/home/auth-providers/{provider_id}) to connect to users' {toolkit_name} accounts. Please refer to the [{provider_name} auth provider](/home/auth-providers/{provider_id}) documentation to learn how to configure auth."
|
||||||
|
|
||||||
GENERIC_PROVIDER_CONFIG = "The {toolkit_name} toolkit uses the Auth Provider with id `{provider_id}` to connect to users' {toolkit_name} accounts. In order to use the toolkit, you will need to configure the `{provider_id}` auth provider."
|
GENERIC_PROVIDER_CONFIG = "The {toolkit_name} MCP Server uses the Auth Provider with id `{provider_id}` to connect to users' {toolkit_name} accounts. In order to use the MCP Server, you will need to configure the `{provider_id}` auth provider."
|
||||||
|
|
||||||
TOOL_CALL_EXAMPLE_JS = """import {{ Arcade }} from "@arcadeai/arcadejs";
|
TOOL_CALL_EXAMPLE_JS = """import {{ Arcade }} from "@arcadeai/arcadejs";
|
||||||
|
|
||||||
|
|
@ -148,7 +155,7 @@ print(json.dumps(response.output.value, indent=2))
|
||||||
|
|
||||||
ENUM_MDX = """## Reference
|
ENUM_MDX = """## Reference
|
||||||
|
|
||||||
Below is a reference of enumerations used by some of the tools in the {toolkit_name} toolkit:
|
Below is a reference of enumerations used by some of the tools in the {toolkit_name} MCP Server:
|
||||||
|
|
||||||
{enum_items}
|
{enum_items}
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,19 @@ import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 11):
|
||||||
|
import tomllib
|
||||||
|
else:
|
||||||
|
tomllib = None
|
||||||
|
|
||||||
from arcade_core.auth import AuthProviderType
|
from arcade_core.auth import AuthProviderType
|
||||||
from arcade_core.catalog import ToolCatalog
|
from arcade_core.catalog import ToolCatalog
|
||||||
from arcade_core.schema import ToolAuthRequirement, ToolDefinition
|
from arcade_core.schema import ToolDefinition, ToolRequirements
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
from arcade_cli.utils import discover_toolkits
|
from arcade_cli.utils import discover_toolkits
|
||||||
|
|
@ -42,14 +48,22 @@ def write_file(path: str, content: str) -> None:
|
||||||
|
|
||||||
def read_toolkit_metadata(toolkit_dir: str) -> str:
|
def read_toolkit_metadata(toolkit_dir: str) -> str:
|
||||||
pyproject_path = os.path.join(toolkit_dir, "pyproject.toml")
|
pyproject_path = os.path.join(toolkit_dir, "pyproject.toml")
|
||||||
with open(pyproject_path) as f:
|
|
||||||
content = f.read()
|
if tomllib is not None:
|
||||||
project_section_match = re.search(r"\[project\](.*?)(?=\n\[|$)", content, re.DOTALL)
|
with open(pyproject_path, "rb") as f:
|
||||||
if project_section_match:
|
data = tomllib.load(f)
|
||||||
project_content = project_section_match.group(1)
|
if "project" in data and "name" in data["project"]:
|
||||||
name_match = re.search(r'name\s*=\s*["\']([^"\']+)["\']', project_content)
|
return data["project"]["name"]
|
||||||
if name_match:
|
else:
|
||||||
return name_match.group(1).strip()
|
# Fallback to regex for Python < 3.11
|
||||||
|
with open(pyproject_path) as f:
|
||||||
|
content = f.read()
|
||||||
|
project_section_match = re.search(r"\[project\](.*?)(?=\n\[|$)", content, re.DOTALL)
|
||||||
|
if project_section_match:
|
||||||
|
project_content = project_section_match.group(1)
|
||||||
|
name_match = re.search(r'name\s*=\s*["\']([^"\']+)["\']', project_content)
|
||||||
|
if name_match:
|
||||||
|
return name_match.group(1).strip()
|
||||||
|
|
||||||
raise ValueError(f"Could not find package name in '{pyproject_path}'")
|
raise ValueError(f"Could not find package name in '{pyproject_path}'")
|
||||||
|
|
||||||
|
|
@ -113,14 +127,15 @@ def get_all_enumerations(toolkit_root_dir: str) -> dict[str, type[Enum]]:
|
||||||
return enums
|
return enums
|
||||||
|
|
||||||
|
|
||||||
def get_toolkit_auth_type(requirement: ToolAuthRequirement | None) -> str:
|
def get_toolkit_auth_type(tool_req: ToolRequirements | None) -> str:
|
||||||
if requirement is None:
|
if tool_req.authorization:
|
||||||
return ""
|
if tool_req.authorization.provider_type == AuthProviderType.oauth2.value:
|
||||||
elif requirement.provider_type == AuthProviderType.oauth2.value:
|
return 'authType="OAuth2"'
|
||||||
return 'authType="OAuth2"'
|
else:
|
||||||
elif requirement.provider_type:
|
return f'authType="{tool_req.authorization.provider_type}"'
|
||||||
return f'authType="{requirement.provider_type}"'
|
elif tool_req.secrets:
|
||||||
return ""
|
return 'authType="API Key"'
|
||||||
|
return 'authType="None"'
|
||||||
|
|
||||||
|
|
||||||
def find_enum_by_options(
|
def find_enum_by_options(
|
||||||
|
|
@ -159,3 +174,47 @@ def is_well_known_provider(
|
||||||
|
|
||||||
def clean_fully_qualified_name(fully_qualified_name: str) -> str:
|
def clean_fully_qualified_name(fully_qualified_name: str) -> str:
|
||||||
return fully_qualified_name.split("@")[0]
|
return fully_qualified_name.split("@")[0]
|
||||||
|
|
||||||
|
|
||||||
|
def has_wrapper_tools_directory(toolkit_package_path: str) -> bool:
|
||||||
|
has_dir = os.path.exists(os.path.join(toolkit_package_path, "wrapper_tools"))
|
||||||
|
if has_dir:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check one level deep
|
||||||
|
for dir_name in os.listdir(toolkit_package_path):
|
||||||
|
if os.path.exists(os.path.join(toolkit_package_path, dir_name, "wrapper_tools")):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def find_pyproject_toml(toolkit_package_path: str) -> str:
|
||||||
|
for root, _, files in os.walk(toolkit_package_path):
|
||||||
|
for file in files:
|
||||||
|
if file == "pyproject.toml":
|
||||||
|
return os.path.join(root, file)
|
||||||
|
|
||||||
|
raise ValueError(f"No pyproject.toml found in {toolkit_package_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_pyproject_description(pyproject_path: str) -> str:
|
||||||
|
if tomllib is not None:
|
||||||
|
with open(pyproject_path, "rb") as f:
|
||||||
|
data = tomllib.load(f)
|
||||||
|
if "project" in data and "description" in data["project"]:
|
||||||
|
return data["project"]["description"]
|
||||||
|
else:
|
||||||
|
# Fallback to regex for Python < 3.11
|
||||||
|
with open(pyproject_path) as f:
|
||||||
|
content = f.read()
|
||||||
|
project_section_match = re.search(r"\[project\](.*?)(?=\n\[|$)", content, re.DOTALL)
|
||||||
|
if project_section_match:
|
||||||
|
project_content = project_section_match.group(1)
|
||||||
|
description_match = re.search(
|
||||||
|
r'description\s*=\s*["\']([^"\']+)["\']', project_content
|
||||||
|
)
|
||||||
|
if description_match:
|
||||||
|
return description_match.group(1).strip()
|
||||||
|
|
||||||
|
raise ValueError(f"Could not find description in '{pyproject_path}'")
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,10 @@ from arcade_core.schema import ToolAuthRequirement
|
||||||
|
|
||||||
|
|
||||||
@patch("arcade_cli.toolkit_docs.utils.open")
|
@patch("arcade_cli.toolkit_docs.utils.open")
|
||||||
def test_read_toolkit_metadata(mock_open):
|
@patch("arcade_cli.toolkit_docs.utils.tomllib")
|
||||||
|
def test_read_toolkit_metadata(mock_tomllib, mock_open):
|
||||||
|
from unittest.mock import MagicMock, mock_open as mock_open_func
|
||||||
|
|
||||||
sample_pyproject_toml = """
|
sample_pyproject_toml = """
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = [ "hatchling",]
|
requires = [ "hatchling",]
|
||||||
|
|
@ -74,13 +77,25 @@ skip_empty = true
|
||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = [ "arcade_jira",]
|
packages = [ "arcade_jira",]
|
||||||
"""
|
"""
|
||||||
mock_open.return_value.__enter__.return_value.read.return_value = sample_pyproject_toml
|
|
||||||
|
# Setup mock to handle both binary and text mode
|
||||||
|
def open_side_effect(path, mode="r"):
|
||||||
|
if mode == "rb":
|
||||||
|
return mock_open_func(read_data=sample_pyproject_toml.encode()).return_value
|
||||||
|
else:
|
||||||
|
return mock_open_func(read_data=sample_pyproject_toml).return_value
|
||||||
|
|
||||||
|
mock_open.side_effect = open_side_effect
|
||||||
|
mock_tomllib.load.return_value = {"project": {"name": "arcade_jira"}}
|
||||||
|
|
||||||
assert read_toolkit_metadata("path/to/toolkits/jira") == "arcade_jira"
|
assert read_toolkit_metadata("path/to/toolkits/jira") == "arcade_jira"
|
||||||
mock_open.assert_called_once_with("path/to/toolkits/jira/pyproject.toml")
|
|
||||||
|
|
||||||
|
|
||||||
@patch("arcade_cli.toolkit_docs.utils.open")
|
@patch("arcade_cli.toolkit_docs.utils.open")
|
||||||
def test_read_toolkit_metadata_missing_project_name(mock_open):
|
@patch("arcade_cli.toolkit_docs.utils.tomllib")
|
||||||
|
def test_read_toolkit_metadata_missing_project_name(mock_tomllib, mock_open):
|
||||||
|
from unittest.mock import mock_open as mock_open_func
|
||||||
|
|
||||||
sample_pyproject_toml = """
|
sample_pyproject_toml = """
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = [ "hatchling",]
|
requires = [ "hatchling",]
|
||||||
|
|
@ -98,9 +113,19 @@ dependencies = [
|
||||||
name = "Arcade"
|
name = "Arcade"
|
||||||
email = "dev@arcade.dev"
|
email = "dev@arcade.dev"
|
||||||
"""
|
"""
|
||||||
mock_open.return_value.__enter__.return_value.read.return_value = sample_pyproject_toml
|
|
||||||
|
# Setup mock to handle both binary and text mode
|
||||||
|
def open_side_effect(path, mode="r"):
|
||||||
|
if mode == "rb":
|
||||||
|
return mock_open_func(read_data=sample_pyproject_toml.encode()).return_value
|
||||||
|
else:
|
||||||
|
return mock_open_func(read_data=sample_pyproject_toml).return_value
|
||||||
|
|
||||||
|
mock_open.side_effect = open_side_effect
|
||||||
|
mock_tomllib.load.return_value = {"project": {}} # Missing "name"
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
print("\n\n\n", read_toolkit_metadata("path/to/toolkits/jira"))
|
read_toolkit_metadata("path/to/toolkits/jira")
|
||||||
|
|
||||||
|
|
||||||
def test_pascal_to_snake_case():
|
def test_pascal_to_snake_case():
|
||||||
|
|
@ -109,18 +134,23 @@ def test_pascal_to_snake_case():
|
||||||
|
|
||||||
|
|
||||||
def test_get_toolkit_auth_type_none():
|
def test_get_toolkit_auth_type_none():
|
||||||
assert get_toolkit_auth_type(requirement=None) == ""
|
from arcade_core.schema import ToolRequirements
|
||||||
|
|
||||||
|
tool_req = ToolRequirements()
|
||||||
|
assert get_toolkit_auth_type(tool_req=tool_req) == 'authType="None"'
|
||||||
|
|
||||||
|
|
||||||
def test_get_toolkit_auth_type_with_provider_type():
|
def test_get_toolkit_auth_type_with_provider_type():
|
||||||
requirement = ToolAuthRequirement(provider_type=AuthProviderType.oauth2.value)
|
from arcade_core.schema import ToolRequirements, ToolSecretRequirement
|
||||||
assert get_toolkit_auth_type(requirement=requirement) == 'authType="OAuth2"'
|
|
||||||
|
|
||||||
requirement = ToolAuthRequirement(provider_type="another_type")
|
tool_req = ToolRequirements(authorization=ToolAuthRequirement(provider_type=AuthProviderType.oauth2.value))
|
||||||
assert get_toolkit_auth_type(requirement=requirement) == 'authType="another_type"'
|
assert get_toolkit_auth_type(tool_req=tool_req) == 'authType="OAuth2"'
|
||||||
|
|
||||||
requirement = ToolAuthRequirement(provider_type="")
|
tool_req = ToolRequirements(authorization=ToolAuthRequirement(provider_type="another_type"))
|
||||||
assert get_toolkit_auth_type(requirement=requirement) == ""
|
assert get_toolkit_auth_type(tool_req=tool_req) == 'authType="another_type"'
|
||||||
|
|
||||||
|
tool_req = ToolRequirements(secrets=[ToolSecretRequirement(key="API_KEY")])
|
||||||
|
assert get_toolkit_auth_type(tool_req=tool_req) == 'authType="API Key"'
|
||||||
|
|
||||||
|
|
||||||
def test_is_well_known_provider_none():
|
def test_is_well_known_provider_none():
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "arcade-mcp"
|
name = "arcade-mcp"
|
||||||
version = "1.0.0rc2"
|
version = "1.0.0rc3"
|
||||||
description = "Arcade.dev - Tool Calling platform for Agents"
|
description = "Arcade.dev - Tool Calling platform for Agents"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue