fix: mypy errors silently dropped during CI (#832)
Resolves https://linear.app/arcadedev/issue/TOO-788/mypy-failures-are-silently-dropped-during-arcade-mcp-ci <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: primarily CI/Makefile behavior and type-annotation tweaks; functional logic is unchanged aside from stricter failure propagation in `make check`. > > **Overview** > **Stops CI from silently ignoring mypy failures.** The `make check` target now runs `mypy` across `libs/arcade*/` and exits non-zero if any package fails, reporting the failed libs. > > Separately tightens typing to satisfy `mypy` (removing `type: ignore` on OAuth helpers, adding `cast()`/`Any` annotations for JSON response shapes and subprocess kwargs, and handling non-`str` `server_address` hosts), and bumps patch versions for `arcade-mcp` and `arcade-mcp-server`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit e79575b13a2d03adf3548104a0064c643f1e21b1. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
This commit is contained in:
parent
be52f07930
commit
cbe68462df
7 changed files with 27 additions and 20 deletions
14
Makefile
14
Makefile
|
|
@ -10,11 +10,15 @@ install: ## Install the uv environment and all packages with dependencies
|
|||
check: ## Run code quality tools.
|
||||
@echo "🚀 Linting code: Running pre-commit"
|
||||
@uv run pre-commit run -a
|
||||
@echo "🚀 Static type checking: Running mypy on libs"
|
||||
@for lib in libs/arcade*/ ; do \
|
||||
echo "🔍 Type checking $$lib"; \
|
||||
(cd $$lib && uv run mypy . --exclude tests || true); \
|
||||
done
|
||||
@echo "🚀 Static type checking: Running mypy on libs"
|
||||
@failed_libs=""; for lib in libs/arcade*/ ; do \
|
||||
echo "🔍 Type checking $$lib"; \
|
||||
(cd $$lib && uv run mypy . --exclude tests) || failed_libs="$$failed_libs $$lib"; \
|
||||
done; \
|
||||
if [ -n "$$failed_libs" ]; then \
|
||||
echo "❌ mypy failed in:$$failed_libs"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: check-libs
|
||||
check-libs: ## Run code quality tools for each lib package
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ def _get_default_oauth_timeout_seconds() -> int:
|
|||
DEFAULT_OAUTH_TIMEOUT_SECONDS = _get_default_oauth_timeout_seconds()
|
||||
|
||||
|
||||
def create_oauth_client(cli_config: CLIConfig) -> OAuth2Client: # type: ignore[no-any-unimported]
|
||||
def create_oauth_client(cli_config: CLIConfig) -> OAuth2Client:
|
||||
"""
|
||||
Create an authlib OAuth2Client configured for the CLI.
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ def create_oauth_client(cli_config: CLIConfig) -> OAuth2Client: # type: ignore[
|
|||
)
|
||||
|
||||
|
||||
def generate_authorization_url( # type: ignore[no-any-unimported]
|
||||
def generate_authorization_url(
|
||||
client: OAuth2Client,
|
||||
cli_config: CLIConfig,
|
||||
redirect_uri: str,
|
||||
|
|
@ -123,7 +123,7 @@ def generate_authorization_url( # type: ignore[no-any-unimported]
|
|||
return url, code_verifier
|
||||
|
||||
|
||||
def exchange_code_for_tokens( # type: ignore[no-any-unimported]
|
||||
def exchange_code_for_tokens(
|
||||
client: OAuth2Client,
|
||||
code: str,
|
||||
redirect_uri: str,
|
||||
|
|
@ -321,7 +321,7 @@ class _LoopbackHTTPServer(HTTPServer):
|
|||
def server_bind(self) -> None:
|
||||
socketserver.TCPServer.server_bind(self)
|
||||
host, port = self.server_address[:2]
|
||||
self.server_name = host
|
||||
self.server_name = host if isinstance(host, str) else host.decode()
|
||||
self.server_port = port
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import json as _json
|
|||
import logging
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
import httpx
|
||||
from arcade_core.constants import PROD_COORDINATOR_HOST, PROD_ENGINE_HOST
|
||||
|
|
@ -55,7 +56,7 @@ def _read_cache(debug: bool = False) -> dict[str, list[str]] | None:
|
|||
return None
|
||||
if debug:
|
||||
console.print(f" [dim]Using cached tool catalog ({age:.0f}s old)[/dim]")
|
||||
return data.get("toolkits", {})
|
||||
return cast("dict[str, list[str]]", data.get("toolkits", {}))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
|
@ -318,7 +319,7 @@ def list_gateways(
|
|||
return []
|
||||
|
||||
data = resp.json()
|
||||
return data.get("items", [])
|
||||
return cast("list[dict[Any, Any]]", data.get("items", []))
|
||||
|
||||
|
||||
def find_matching_gateway(
|
||||
|
|
@ -401,13 +402,13 @@ def create_gateway(
|
|||
if resp.status_code not in (200, 201):
|
||||
raise RuntimeError(f"Failed to create gateway ({resp.status_code}): {resp.text}")
|
||||
|
||||
data = resp.json()
|
||||
data: dict[Any, Any] = resp.json()
|
||||
|
||||
# The API may return the gateway directly or wrapped in a list/items envelope
|
||||
if "slug" in data:
|
||||
return data
|
||||
if data.get("items"):
|
||||
return data["items"][0]
|
||||
return cast("dict[Any, Any]", data["items"][0])
|
||||
if "id" in data:
|
||||
return data
|
||||
|
||||
|
|
@ -695,13 +696,13 @@ def _resolve_gateway_slug(
|
|||
if gw.get("slug", "").lower() == input_lower:
|
||||
if debug:
|
||||
console.print(f" [dim]Matched by slug: {gw['slug']}[/dim]")
|
||||
return gw["slug"]
|
||||
return cast("str", gw["slug"])
|
||||
for gw in gateways:
|
||||
if gw.get("name", "").lower() == input_lower:
|
||||
slug = gw["slug"]
|
||||
if debug:
|
||||
console.print(f" [dim]Matched by name '{gw['name']}' -> slug: {slug}[/dim]")
|
||||
return slug
|
||||
return cast("str", slug)
|
||||
if debug:
|
||||
available = [f"{g.get('name')} ({g.get('slug')})" for g in gateways]
|
||||
console.print(f" [dim]No match for '{user_input}', available: {available}[/dim]")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import os
|
|||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
import click
|
||||
import typer
|
||||
|
|
@ -334,7 +334,7 @@ def mcp(
|
|||
# window from appearing (e.g. when an MCP client spawns this
|
||||
# command without an attached console). The child process still
|
||||
# inherits stdin/stdout/stderr for stdio transport communication.
|
||||
run_kwargs: dict[str, object] = {"check": False}
|
||||
run_kwargs: dict[str, Any] = {"check": False}
|
||||
creation_flags = get_windows_no_window_creationflags()
|
||||
if creation_flags:
|
||||
run_kwargs["creationflags"] = creation_flags
|
||||
|
|
|
|||
|
|
@ -184,7 +184,9 @@ def build_input_schema_from_definition(definition: ToolDefinition) -> dict[str,
|
|||
if getattr(definition, "input", None) and getattr(definition.input, "parameters", None):
|
||||
for param in definition.input.parameters:
|
||||
val_schema = getattr(param, "value_schema", None)
|
||||
schema = _value_schema_to_json_schema(val_schema) if val_schema else {"type": "string"}
|
||||
schema: dict[str, Any] = (
|
||||
_value_schema_to_json_schema(val_schema) if val_schema else {"type": "string"}
|
||||
)
|
||||
|
||||
if getattr(param, "description", None):
|
||||
schema["description"] = param.description
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||
|
||||
[project]
|
||||
name = "arcade-mcp-server"
|
||||
version = "1.21.0"
|
||||
version = "1.21.1"
|
||||
description = "Model Context Protocol (MCP) server framework for Arcade.dev"
|
||||
readme = "README.md"
|
||||
authors = [{ name = "Arcade.dev" }]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "arcade-mcp"
|
||||
version = "1.14.0"
|
||||
version = "1.14.1"
|
||||
description = "Arcade.dev - Tool Calling platform for Agents"
|
||||
readme = "README.md"
|
||||
license = { file = "LICENSE" }
|
||||
|
|
|
|||
Loading…
Reference in a new issue