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.
|
check: ## Run code quality tools.
|
||||||
@echo "🚀 Linting code: Running pre-commit"
|
@echo "🚀 Linting code: Running pre-commit"
|
||||||
@uv run pre-commit run -a
|
@uv run pre-commit run -a
|
||||||
@echo "🚀 Static type checking: Running mypy on libs"
|
@echo "🚀 Static type checking: Running mypy on libs"
|
||||||
@for lib in libs/arcade*/ ; do \
|
@failed_libs=""; for lib in libs/arcade*/ ; do \
|
||||||
echo "🔍 Type checking $$lib"; \
|
echo "🔍 Type checking $$lib"; \
|
||||||
(cd $$lib && uv run mypy . --exclude tests || true); \
|
(cd $$lib && uv run mypy . --exclude tests) || failed_libs="$$failed_libs $$lib"; \
|
||||||
done
|
done; \
|
||||||
|
if [ -n "$$failed_libs" ]; then \
|
||||||
|
echo "❌ mypy failed in:$$failed_libs"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
.PHONY: check-libs
|
.PHONY: check-libs
|
||||||
check-libs: ## Run code quality tools for each lib package
|
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()
|
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.
|
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,
|
client: OAuth2Client,
|
||||||
cli_config: CLIConfig,
|
cli_config: CLIConfig,
|
||||||
redirect_uri: str,
|
redirect_uri: str,
|
||||||
|
|
@ -123,7 +123,7 @@ def generate_authorization_url( # type: ignore[no-any-unimported]
|
||||||
return url, code_verifier
|
return url, code_verifier
|
||||||
|
|
||||||
|
|
||||||
def exchange_code_for_tokens( # type: ignore[no-any-unimported]
|
def exchange_code_for_tokens(
|
||||||
client: OAuth2Client,
|
client: OAuth2Client,
|
||||||
code: str,
|
code: str,
|
||||||
redirect_uri: str,
|
redirect_uri: str,
|
||||||
|
|
@ -321,7 +321,7 @@ class _LoopbackHTTPServer(HTTPServer):
|
||||||
def server_bind(self) -> None:
|
def server_bind(self) -> None:
|
||||||
socketserver.TCPServer.server_bind(self)
|
socketserver.TCPServer.server_bind(self)
|
||||||
host, port = self.server_address[:2]
|
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
|
self.server_port = port
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import json as _json
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any, cast
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from arcade_core.constants import PROD_COORDINATOR_HOST, PROD_ENGINE_HOST
|
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
|
return None
|
||||||
if debug:
|
if debug:
|
||||||
console.print(f" [dim]Using cached tool catalog ({age:.0f}s old)[/dim]")
|
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:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -318,7 +319,7 @@ def list_gateways(
|
||||||
return []
|
return []
|
||||||
|
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
return data.get("items", [])
|
return cast("list[dict[Any, Any]]", data.get("items", []))
|
||||||
|
|
||||||
|
|
||||||
def find_matching_gateway(
|
def find_matching_gateway(
|
||||||
|
|
@ -401,13 +402,13 @@ def create_gateway(
|
||||||
if resp.status_code not in (200, 201):
|
if resp.status_code not in (200, 201):
|
||||||
raise RuntimeError(f"Failed to create gateway ({resp.status_code}): {resp.text}")
|
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
|
# The API may return the gateway directly or wrapped in a list/items envelope
|
||||||
if "slug" in data:
|
if "slug" in data:
|
||||||
return data
|
return data
|
||||||
if data.get("items"):
|
if data.get("items"):
|
||||||
return data["items"][0]
|
return cast("dict[Any, Any]", data["items"][0])
|
||||||
if "id" in data:
|
if "id" in data:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
@ -695,13 +696,13 @@ def _resolve_gateway_slug(
|
||||||
if gw.get("slug", "").lower() == input_lower:
|
if gw.get("slug", "").lower() == input_lower:
|
||||||
if debug:
|
if debug:
|
||||||
console.print(f" [dim]Matched by slug: {gw['slug']}[/dim]")
|
console.print(f" [dim]Matched by slug: {gw['slug']}[/dim]")
|
||||||
return gw["slug"]
|
return cast("str", gw["slug"])
|
||||||
for gw in gateways:
|
for gw in gateways:
|
||||||
if gw.get("name", "").lower() == input_lower:
|
if gw.get("name", "").lower() == input_lower:
|
||||||
slug = gw["slug"]
|
slug = gw["slug"]
|
||||||
if debug:
|
if debug:
|
||||||
console.print(f" [dim]Matched by name '{gw['name']}' -> slug: {slug}[/dim]")
|
console.print(f" [dim]Matched by name '{gw['name']}' -> slug: {slug}[/dim]")
|
||||||
return slug
|
return cast("str", slug)
|
||||||
if debug:
|
if debug:
|
||||||
available = [f"{g.get('name')} ({g.get('slug')})" for g in gateways]
|
available = [f"{g.get('name')} ({g.get('slug')})" for g in gateways]
|
||||||
console.print(f" [dim]No match for '{user_input}', available: {available}[/dim]")
|
console.print(f" [dim]No match for '{user_input}', available: {available}[/dim]")
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import typer
|
import typer
|
||||||
|
|
@ -334,7 +334,7 @@ def mcp(
|
||||||
# window from appearing (e.g. when an MCP client spawns this
|
# window from appearing (e.g. when an MCP client spawns this
|
||||||
# command without an attached console). The child process still
|
# command without an attached console). The child process still
|
||||||
# inherits stdin/stdout/stderr for stdio transport communication.
|
# 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()
|
creation_flags = get_windows_no_window_creationflags()
|
||||||
if creation_flags:
|
if creation_flags:
|
||||||
run_kwargs["creationflags"] = 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):
|
if getattr(definition, "input", None) and getattr(definition.input, "parameters", None):
|
||||||
for param in definition.input.parameters:
|
for param in definition.input.parameters:
|
||||||
val_schema = getattr(param, "value_schema", None)
|
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):
|
if getattr(param, "description", None):
|
||||||
schema["description"] = param.description
|
schema["description"] = param.description
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "arcade-mcp-server"
|
name = "arcade-mcp-server"
|
||||||
version = "1.21.0"
|
version = "1.21.1"
|
||||||
description = "Model Context Protocol (MCP) server framework for Arcade.dev"
|
description = "Model Context Protocol (MCP) server framework for Arcade.dev"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [{ name = "Arcade.dev" }]
|
authors = [{ name = "Arcade.dev" }]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "arcade-mcp"
|
name = "arcade-mcp"
|
||||||
version = "1.14.0"
|
version = "1.14.1"
|
||||||
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