arcade new <name> --full generates an MCPApp (#787)

<!-- CURSOR_SUMMARY -->
> [!NOTE]
> **Medium Risk**
> Moderate risk because it changes the default `arcade new --full`
scaffolding (dependencies, entry points, Makefile workflow, licensing)
and removes interactive prompts, which could break existing expectations
for generated projects.
> 
> **Overview**
> `arcade new --full` scaffolding is simplified to be non-interactive
and always generate an `arcade_<name>` package, with derived name
variants (title/hyphenated) and updated next-step instructions (`make
install/dev/test/lint`).
> 
> The full template is updated to produce a runnable `arcade-mcp-server`
`MCPApp` (`__main__.py` + `project.scripts`), switch sample
code/tests/evals to a new async Reddit example tool (auth + metadata)
using `httpx`, and refresh dev tooling (new `Makefile`, ruff config
tweaks, added pytest `conftest.py`).
> 
> Template licensing/metadata is standardized to Arcade proprietary
(removes community/official conditionals and the templated README), and
the repo version is bumped to `1.12.0`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
fd39c9ed9beba068fe85cf96979f04a31a40daa4. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
This commit is contained in:
Eric Gustin 2026-03-05 17:50:10 -08:00 committed by GitHub
parent 830480de83
commit 4d48bb765d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 198 additions and 300 deletions

View file

@ -3,9 +3,7 @@ import shutil
from datetime import datetime
from importlib.metadata import version as get_version
from pathlib import Path
from typing import Optional
import typer
from jinja2 import Environment, FileSystemLoader, select_autoescape
from arcade_cli.console import console
@ -20,48 +18,10 @@ except Exception as e:
ARCADE_MCP_MIN_VERSION = "1.10.0" # Default version if unable to fetch
ARCADE_MCP_MAX_VERSION = "2.0.0"
ARCADE_TDK_MIN_VERSION = "3.6.0"
ARCADE_TDK_MAX_VERSION = "4.0.0"
ARCADE_SERVE_MIN_VERSION = "3.1.5"
ARCADE_SERVE_MAX_VERSION = "4.0.0"
ARCADE_MCP_SERVER_MIN_VERSION = "1.17.0"
ARCADE_MCP_SERVER_MAX_VERSION = "2.0.0"
def ask_question(question: str, default: Optional[str] = None) -> str:
"""
Ask a question via input() and return the answer.
"""
answer = typer.prompt(question, default=default, show_default=False)
if not answer and default:
return default
return str(answer)
def ask_yes_no_question(question: str, default: bool = True) -> bool:
"""
Ask a yes/no question via input() and return the bool answer.
"""
default_str = "Y/n" if default else "y/N"
answer = typer.prompt(
f"{question} ({default_str})", default="y" if default else "n", show_default=False
)
return answer.lower() in [
"y",
"y/",
"yes",
"true",
"1",
"ye",
"yes",
"yeah",
"yep",
"sure",
"ok",
"yup",
]
def render_template(env: Environment, template_string: str, context: dict) -> str:
"""Render a template string with the given variables."""
template = env.from_string(template_string)
@ -73,9 +33,7 @@ def write_template(path: Path, content: str) -> None:
path.write_text(content, encoding="utf-8")
def create_ignore_pattern(
include_evals: bool, is_community_or_official_toolkit: bool
) -> re.Pattern[str]:
def create_ignore_pattern(include_evals: bool) -> re.Pattern[str]:
"""Create an ignore pattern based on user preferences."""
patterns = [
"__pycache__",
@ -96,11 +54,6 @@ def create_ignore_pattern(
if not include_evals:
patterns.append("evals")
if not is_community_or_official_toolkit:
patterns.extend([".ruff.toml", ".pre-commit-config.yaml", "LICENSE"])
else:
patterns.extend(["README.md"])
return re.compile(f"({'|'.join(patterns)})$")
@ -166,47 +119,21 @@ def create_new_toolkit(output_directory: str, toolkit_name: str) -> None:
)
exit(1)
toolkit_description = ask_question("Describe what your server will do (optional)", default="")
toolkit_author_name = ask_question("Your GitHub username (optional)", default="")
while True:
toolkit_author_email = ask_question("Your email (optional)", default="")
if toolkit_author_email == "" or re.match(r"[^@ ]+@[^@ ]+\.[^@ ]+", toolkit_author_email):
break
console.print(
"[red]Invalid email format. Please enter a valid email address or leave it empty.[/red]"
)
include_evals = ask_yes_no_question(
"Do you want an evals directory created for you?", default=True
)
cwd = Path.cwd()
# TODO: this detection mechanism works only for people that didn't change the
# name of the repo, a better detection method is required here
is_community_toolkit = False
if cwd.name == "toolkits" and cwd.parent.name == "arcade-mcp":
prompt = (
"Is your server a community contribution (to be merged into "
"\x1b]8;;https://github.com/ArcadeAI/arcade-mcp\x1b\\ArcadeAI/arcade-mcp\x1b]8;;\x1b\\ repo)?"
)
is_community_toolkit = ask_yes_no_question(prompt, default=True)
is_official_toolkit = cwd.name == "toolkits" and cwd.parent.name == "tools"
toolkit_name_title = toolkit_name.replace("_", " ").title()
toolkit_name_hyphenated = toolkit_name.replace("_", "-")
toolkit_description = f"Arcade.dev tools for interacting with {toolkit_name_title}"
context = {
"package_name": "arcade_" + toolkit_name if is_community_toolkit else toolkit_name,
"package_name": "arcade_" + toolkit_name,
"toolkit_name": toolkit_name,
"toolkit_name_title": toolkit_name_title,
"toolkit_name_hyphenated": toolkit_name_hyphenated,
"toolkit_description": toolkit_description,
"toolkit_author_name": toolkit_author_name,
"toolkit_author_email": toolkit_author_email,
"arcade_tdk_min_version": ARCADE_TDK_MIN_VERSION,
"arcade_tdk_max_version": ARCADE_TDK_MAX_VERSION,
"arcade_serve_min_version": ARCADE_SERVE_MIN_VERSION,
"arcade_serve_max_version": ARCADE_SERVE_MAX_VERSION,
"arcade_mcp_server_min_version": ARCADE_MCP_SERVER_MIN_VERSION,
"arcade_mcp_server_max_version": ARCADE_MCP_SERVER_MAX_VERSION,
"arcade_mcp_min_version": ARCADE_MCP_MIN_VERSION,
"arcade_mcp_max_version": ARCADE_MCP_MAX_VERSION,
"creation_year": datetime.now().year,
"is_community_toolkit": is_community_toolkit,
"is_official_toolkit": is_official_toolkit,
}
template_directory = get_full_template_directory() / "{{ toolkit_name }}"
@ -214,12 +141,10 @@ def create_new_toolkit(output_directory: str, toolkit_name: str) -> None:
env = Environment(
loader=FileSystemLoader(str(template_directory)),
autoescape=select_autoescape(["html", "xml"]),
keep_trailing_newline=True,
)
# Create dynamic ignore pattern based on user preferences
ignore_pattern = create_ignore_pattern(
include_evals, is_community_toolkit or is_official_toolkit
)
ignore_pattern = create_ignore_pattern(include_evals=True)
try:
create_package(env, template_directory, toolkit_directory, context, ignore_pattern)
@ -228,23 +153,16 @@ def create_new_toolkit(output_directory: str, toolkit_name: str) -> None:
)
console.print("\nNext steps:", style="bold")
console.print(f" 1. cd {toolkit_directory / toolkit_name}")
console.print(" 2. make install")
console.print(" 3. make dev # serve with MCP and worker endpoints")
console.print(" 4. make test # run tests")
console.print(" 5. make lint # run linting")
console.print("")
console.print(" 2. Run the server (choose one transport):", style="dim")
console.print(" - stdio: uv run server.py")
console.print(" - http: uv run server.py --transport http --port 8000")
console.print("")
create_deployment(toolkit_directory, toolkit_name)
except Exception:
remove_toolkit(toolkit_directory, toolkit_name)
raise
def create_deployment(toolkit_directory: Path, toolkit_name: str) -> None:
# No longer create worker.toml for MCP servers
# The server.py file handles all configuration
pass
def create_new_toolkit_minimal(output_directory: str, toolkit_name: str) -> None:
"""Create a new toolkit from a template with user input."""
toolkit_directory = Path(output_directory)
@ -274,9 +192,10 @@ def create_new_toolkit_minimal(output_directory: str, toolkit_name: str) -> None
env = Environment(
loader=FileSystemLoader(str(template_directory)),
autoescape=select_autoescape(["html", "xml"]),
keep_trailing_newline=True,
)
ignore_pattern = create_ignore_pattern(False, False)
ignore_pattern = create_ignore_pattern(False)
try:
create_package(env, template_directory, toolkit_directory, context, ignore_pattern)

View file

@ -37,7 +37,9 @@ select = [
]
[lint.per-file-ignores]
"**/tests/*" = ["S101"]
"*" = ["TRY003", "B904"]
"**/tests/*" = ["S101", "E501"]
"**/evals/*" = ["S101", "E501"]
[format]
preview = true

View file

@ -1,4 +1,3 @@
{% if is_official_toolkit -%}
# The Arcade Software License Agreement
- Version 1.0
@ -6,7 +5,7 @@
---
This software and associated documentation (collectively, the “Software”) are the intellectual property of Arcade Technologies, Inc. (“Arcade”). All rights are reserved.
This software and associated documentation (collectively, the "Software") are the intellectual property of Arcade Technologies, Inc. ("Arcade"). All rights are reserved.
1. License Grant
@ -29,32 +28,8 @@ Arcade retains all right, title, and interest in and to the Software, including
5. Disclaimer of Warranty
The Software is provided “as is” without warranty of any kind. Arcade disclaims all warranties, express or implied, including but not limited to warranties of merchantability, fitness for a particular purpose, and noninfringement.
The Software is provided "as is" without warranty of any kind. Arcade disclaims all warranties, express or implied, including but not limited to warranties of merchantability, fitness for a particular purpose, and noninfringement.
6. Limitation of Liability
In no event shall Arcade be liable for any damages arising out of or in connection with the use or performance of the Software, whether in an action of contract, tort (including negligence), or otherwise.
{% endif -%}
{% if is_community_toolkit -%}
MIT License
Copyright (c) 2025, Arcade AI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
{% endif -%}

View file

@ -1,57 +1,31 @@
.PHONY: help
.PHONY: install build test lint dev
help:
@echo "🛠️ github Commands:\n"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
TOOLKIT := $(shell basename $(CURDIR))
.PHONY: install
install: ## Install the uv environment and install all packages with dependencies
@echo "🚀 Creating virtual environment and installing all packages using uv"
@uv sync --active --all-extras --no-sources
@if [ -f .pre-commit-config.yaml ]; then uv run --no-sources pre-commit install; fi
@echo "✅ All packages and dependencies installed via uv"
help: ## Show this help message
@grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*##"}; {printf " \033[36m%-10s\033[0m %s\n", $$1, $$2}'
{% if is_community_toolkit or is_official_toolkit -%}
.PHONY: install-local
install-local: ## Install the uv environment and install all packages with dependencies with local Arcade sources
@echo "🚀 Creating virtual environment and installing all packages using uv"
@uv sync --active --all-extras
@if [ -f .pre-commit-config.yaml ]; then uv run pre-commit install; fi
@echo "✅ All packages and dependencies installed via uv"
{% endif -%}
install: ## Install dependencies, then overlay any ../.local-overrides
uv sync --all-extras
uv run pre-commit install
@if [ -f ../.local-overrides ]; then \
while IFS= read -r pkg || [ -n "$$pkg" ]; do \
case "$$pkg" in \#*|"") continue ;; esac; \
echo "Applying local override: $$pkg"; \
uv pip install -e "$$pkg"; \
done < ../.local-overrides; \
fi
.PHONY: build
build: clean-build ## Build wheel file using poetry
@echo "🚀 Creating wheel file"
build: ## Build wheel
rm -rf dist
uv build
.PHONY: clean-build
clean-build: ## clean build artifacts
@echo "🗑️ Cleaning dist directory"
rm -rf dist
test: ## Run tests with coverage
uv run pytest -W ignore -v --cov --cov-config=pyproject.toml --cov-report=xml
.PHONY: test
test: ## Test the code with pytest
@echo "🚀 Testing code: Running pytest"
@uv run --no-sources pytest -W ignore -v --cov --cov-config=pyproject.toml --cov-report=xml
lint: ## Run linting and type checking
uv run pre-commit run -a
uv run mypy --config-file=pyproject.toml
.PHONY: coverage
coverage: ## Generate coverage report
@echo "coverage report"
@uv run --no-sources coverage report
@echo "Generating coverage report"
@uv run --no-sources coverage html
.PHONY: bump-version
bump-version: ## Bump the version in the pyproject.toml file by a patch version
@echo "🚀 Bumping version in pyproject.toml"
uv version --no-sources --bump patch
.PHONY: check
check: ## Run code quality tools.
@if [ -f .pre-commit-config.yaml ]; then\
echo "🚀 Linting code: Running pre-commit";\
uv run --no-sources pre-commit run -a;\
fi
@echo "🚀 Static type checking: Running mypy"
@uv run --no-sources mypy --config-file=pyproject.toml
dev: ## Run toolkit locally as MCP server
ARCADE_WORKER_SECRET=dev uv run arcade_$(TOOLKIT) http

View file

@ -1,40 +0,0 @@
<div style="display: flex; justify-content: center; align-items: center;">
<img
src="https://docs.arcade.dev/images/logo/arcade-logo.png"
style="width: 250px;"
>
</div>
<div style="display: flex; justify-content: center; align-items: center; margin-bottom: 8px;">
{% if toolkit_author_name -%}
<img src="https://img.shields.io/github/v/release/{{ toolkit_author_name }}/{{ toolkit_name }}" alt="GitHub release" style="margin: 0 2px;">
{% endif -%}
<img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="Python version" style="margin: 0 2px;">
<img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License" style="margin: 0 2px;">
<img src="https://img.shields.io/pypi/v/{{ package_name }}" alt="PyPI version" style="margin: 0 2px;">
</div>
{% if toolkit_author_name -%}
<div style="display: flex; justify-content: center; align-items: center;">
<a href="https://github.com/{{ toolkit_author_name }}/{{ toolkit_name }}" target="_blank">
<img src="https://img.shields.io/github/stars/{{ toolkit_author_name }}/{{ toolkit_name }}" alt="GitHub stars" style="margin: 0 2px;">
</a>
<a href="https://github.com/{{ toolkit_author_name }}/{{ toolkit_name }}/fork" target="_blank">
<img src="https://img.shields.io/github/forks/{{ toolkit_author_name }}/{{ toolkit_name }}" alt="GitHub forks" style="margin: 0 2px;">
</a>
</div>
{% endif %}
<br>
<br>
# Arcade {{ toolkit_name }} Toolkit
{% if toolkit_description -%}
{{ toolkit_description }}
{% endif -%}
## Features
- The {{ toolkit_name }} toolkit does not have any features yet.
## Development
Read the docs on how to create a toolkit [here](https://docs.arcade.dev/en/guides/create-tools/tool-basics/build-mcp-server)

View file

@ -0,0 +1,11 @@
from unittest.mock import AsyncMock, MagicMock
import pytest
@pytest.fixture
def mock_context():
context = AsyncMock()
context.authorization.token = "fake-token" # noqa: S105
context.get_auth_token_or_empty = MagicMock(return_value="fake-token")
return context

View file

@ -1,14 +1,13 @@
from arcade_tdk import ToolCatalog
from arcade_core import ToolCatalog
from arcade_evals import (
EvalRubric,
EvalSuite,
ExpectedToolCall,
tool_eval,
)
from arcade_evals.critic import SimilarityCritic
import {{ package_name }}
from {{ package_name }}.tools.hello import say_hello
from {{ package_name }}.tools.sample import get_my_reddit_profile
# Evaluation rubric
rubric = EvalRubric(
@ -34,17 +33,10 @@ def {{ toolkit_name }}_eval_suite() -> EvalSuite:
)
suite.add_case(
name="Saying hello",
user_message="He's actually right here, say hi to him!",
expected_tool_calls=[ExpectedToolCall(func=say_hello, args={"name": "John Doe"})],
name="Get my Reddit profile",
user_message="What is my Reddit username and karma?",
expected_tool_calls=[ExpectedToolCall(func=get_my_reddit_profile, args={})],
rubric=rubric,
critics=[
SimilarityCritic(critic_field="name", weight=0.5),
],
additional_messages=[
{"role": "user", "content": "My friend's name is John Doe."},
{"role": "assistant", "content": "It is great that you have a friend named John Doe!"},
],
)
return suite

View file

@ -5,56 +5,35 @@ build-backend = "hatchling.build"
[project]
name = "{{ package_name }}"
version = "0.1.0"
{% if toolkit_description -%}
description = "{{ toolkit_description }}"
{% endif -%}
license = {text = "Proprietary - Arcade Software License Agreement v1.0"}
requires-python = ">=3.10"
dependencies = [
"arcade-tdk>={{ arcade_tdk_min_version }},<{{ arcade_tdk_max_version}}",
"arcade-mcp-server>={{ arcade_mcp_server_min_version }},<{{ arcade_mcp_server_max_version }}",
"httpx>=0.27.0,<1.0.0",
]
{% if toolkit_author_name or toolkit_author_email -%}
[project.scripts]
arcade-{{ toolkit_name_hyphenated }} = "{{ package_name }}.__main__:main"
{{ package_name }} = "{{ package_name }}.__main__:main"
[[project.authors]]
{% if toolkit_author_name -%}
name = "{{ toolkit_author_name }}"
{% endif -%}
{% if toolkit_author_email -%}
email = "{{ toolkit_author_email }}"
{% endif -%}
{% endif %}
name = "Arcade"
email = "dev@arcade.dev"
[project.optional-dependencies]
dev = [
"arcade-mcp[evals]>={{ arcade_mcp_min_version }},<{{ arcade_mcp_max_version }}",
"arcade-serve>={{ arcade_serve_min_version }},<{{ arcade_serve_max_version }}",
"pytest>=8.3.0,<8.4.0",
"pytest-cov>=4.0.0,<4.1.0",
"pytest-mock>=3.11.1,<3.12.0",
"pytest-asyncio>=0.24.0,<0.25.0",
"pytest-mock>=3.11.1,<3.12.0",
"mypy>=1.5.1,<1.6.0",
"pre-commit>=3.4.0,<3.5.0",
"tox>=4.11.1,<4.12.0",
"ruff>=0.7.4,<0.8.0",
]
# Tell Arcade.dev that this package is a toolkit
[project.entry-points.arcade_toolkits]
toolkit_name = "{{ package_name }}"
{% if is_community_toolkit -%}
# Use local path sources for arcade libs when working locally
[tool.uv.sources]
arcade-mcp = { path = "../../", editable = true }
arcade-serve = { path = "../../libs/arcade-serve/", editable = true }
arcade-tdk = { path = "../../libs/arcade-tdk/", editable = true }
{% endif -%}
{% if is_official_toolkit -%}
# Use local path sources for arcade libs when working locally
[tool.uv.sources]
arcade-mcp = { path = "../../../arcade-mcp", editable = true }
arcade-serve = { path = "../../../arcade-mcp/libs/arcade-serve/", editable = true }
arcade-tdk = { path = "../../../arcade-mcp/libs/arcade-tdk/", editable = true }
{% endif -%}
[tool.mypy]
files = [ "{{ package_name }}/**/*.py",]
python_version = "3.10"

View file

@ -1,13 +1,25 @@
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from arcade_tdk.errors import ToolExecutionError
from {{ package_name }}.tools.hello import say_hello
from {{ package_name }}.tools.sample import RedditUserProfile, get_my_reddit_profile
def test_hello() -> None:
assert say_hello("developer") == "Hello, developer!"
@pytest.mark.asyncio
async def test_get_my_reddit_profile(mock_context) -> None:
mock_response = MagicMock()
mock_response.json.return_value = {
"name": "test_user",
"comment_karma": 100,
"link_karma": 200,
}
with patch("{{ package_name }}.tools.sample.httpx.AsyncClient") as mock_client:
mock_client.return_value.__aenter__.return_value.get = AsyncMock(return_value=mock_response)
result = await get_my_reddit_profile(mock_context)
def test_hello_raises_error() -> None:
with pytest.raises(ToolExecutionError):
say_hello(1)
assert result == RedditUserProfile(
username="test_user",
comment_karma=100,
link_karma=200,
)

View file

@ -0,0 +1,3 @@
from {{ package_name }}.tools.sample import RedditUserProfile, get_my_reddit_profile
__all__ = ["RedditUserProfile", "get_my_reddit_profile"]

View 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 {{ package_name }}
app = MCPApp(
name="{{ toolkit_name_title }}",
instructions=(
"Use this server when you need to interact with {{ toolkit_name_title }}"
),
)
app.add_tools_from_module({{ package_name }})
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()

View file

@ -1,10 +0,0 @@
from typing import Annotated
from arcade_tdk import tool
@tool
def say_hello(name: Annotated[str, "The name of the person to greet"]) -> str:
"""Say a greeting!"""
return "Hello, " + name + "!"

View file

@ -0,0 +1,62 @@
from typing import Annotated, TypedDict
import httpx
from arcade_mcp_server import Context, tool
from arcade_mcp_server.auth import Reddit
from arcade_mcp_server.metadata import (
Behavior,
Classification,
Operation,
ServiceDomain,
ToolMetadata,
)
REDDIT_API_URL = "https://oauth.reddit.com"
class RedditUserProfile(TypedDict, total=True):
username: str
comment_karma: int | None
link_karma: int | None
@tool(
requires_auth=Reddit(scopes=["identity"]),
metadata=ToolMetadata(
classification=Classification(
service_domains=[ServiceDomain.SOCIAL_MEDIA],
),
behavior=Behavior(
operations=[Operation.READ],
read_only=True,
destructive=False,
idempotent=True,
open_world=True,
),
),
)
async def get_my_reddit_profile(
context: Context,
include_karma: Annotated[
bool, "Whether to include karma breakdown in the response"
] = True,
) -> Annotated[RedditUserProfile, "The authenticated user's Reddit profile"]:
"""Get the Reddit profile of the authenticated user."""
token = context.get_auth_token_or_empty()
async with httpx.AsyncClient() as client:
response = await client.get(
f"{REDDIT_API_URL}/api/v1/me",
headers={"Authorization": f"Bearer {token}"},
)
response.raise_for_status()
data = response.json()
profile = RedditUserProfile(
username=data["name"],
comment_karma=data.get("comment_karma", None) if include_karma else None,
link_karma=data.get("link_karma", None) if include_karma else None,
)
return profile

View file

@ -1,42 +1,33 @@
from io import StringIO
from pathlib import Path
from unittest.mock import patch
import pytest
from arcade_cli.new import create_new_toolkit, create_new_toolkit_minimal
from rich.console import Console
def test_create_new_toolkit_prints_next_steps(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
def test_create_new_toolkit_prints_next_steps(tmp_path: Path) -> None:
"""create_new_toolkit (full template) should print numbered next steps."""
output_dir = tmp_path / "full_test"
output_dir.mkdir()
# Use a cwd that does not trigger community/official toolkit prompts
fake_cwd = tmp_path / "cwd"
fake_cwd.mkdir()
monkeypatch.chdir(fake_cwd)
buf = StringIO()
test_console = Console(file=buf, force_terminal=False)
import arcade_cli.new as new_mod
# Mock prompts: description, author, email, evals (yes)
with patch("arcade_cli.new.typer.prompt", side_effect=["", "", "", "y"]):
buf = StringIO()
test_console = Console(file=buf, force_terminal=False)
import arcade_cli.new as new_mod
orig = new_mod.console
new_mod.console = test_console
try:
create_new_toolkit(str(output_dir), "my_server")
finally:
new_mod.console = orig
orig = new_mod.console
new_mod.console = test_console
try:
create_new_toolkit(str(output_dir), "my_server")
finally:
new_mod.console = orig
output = buf.getvalue()
assert "Next steps:" in output
assert "1. cd " in output
assert "2. Run the server (choose one transport):" in output
assert "- stdio: uv run server.py" in output
assert "- http: uv run server.py --transport http --port 8000" in output
assert "uv run server.py" in output
assert "make install" in output
assert "make dev" in output
assert "make test" in output
assert "my_server" in output

View file

@ -1,6 +1,6 @@
[project]
name = "arcade-mcp"
version = "1.11.2"
version = "1.12.0"
description = "Arcade.dev - Tool Calling platform for Agents"
readme = "README.md"
license = { file = "LICENSE" }