chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105)
Some checks failed
CI / lint (push) Failing after 2s
CI / typecheck (push) Failing after 2s
CI / test (push) Failing after 2s
CI / Secret scan (gitleaks) (push) Failing after 1s
CI / Security audit (bandit) (push) Failing after 2s

Mass ruff auto-fix:
  - ruff check --fix: 109 issues fixed (F401 unused imports,
    I001 unsorted imports, UP037 quoted annotations, SIM105
    suppressible exception, RUF100 unused-noqa)
  - ruff check --fix --unsafe-fixes: 22 additional issues
  - ruff format: 70 files reformatted
  - Manual pass: fix 16 misplaced import httpx lines
  - Manual pass: fix remaining E402 (import-after-docstring)

Result: 283 errors -> 30 errors.

The remaining 30 are real issues that need manual review:
  5 F401 unused-import (likely auto-generated stubs)
  5 F821 undefined-name (real bugs in code that references
    redis/pydantic/LLMRegistry without imports)
  3 BLE001 (the compliance LLM fallback is intentional; the
    other two are real)
  3 RUF012 mutable-class-default
  3 SIM105, 3 SIM117, 2 E722, 2 E741
  1 B007, 1 B025, 1 E402, 1 RUF200 (pyproject.toml issue)

Tests: 436/437 pass (1 pre-existing SSE sandbox failure).
format check + import sort: now clean.
make ci: still gated on the 30 remaining real issues.
Follow-up: triage the 30 issues file-by-file.
This commit is contained in:
Crypto Rug Munch 2026-07-02 21:51:25 +02:00
parent e60a62a07a
commit a7c30b12cd
85 changed files with 2374 additions and 1071 deletions

View file

@ -130,27 +130,34 @@ def set_active_mcp_server(server: Any) -> None:
def notify_resource_changed(uri: str) -> None:
"""Notify all connected MCP clients that a resource has changed."""
_send_mcp_notification({
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {"uri": uri},
})
_send_mcp_notification(
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {"uri": uri},
}
)
def notify_resource_list_changed() -> None:
"""Notify all connected MCP clients that the resource list changed."""
_send_mcp_notification({
"jsonrpc": "2.0",
"method": "notifications/resources/list_changed",
})
_send_mcp_notification(
{
"jsonrpc": "2.0",
"method": "notifications/resources/list_changed",
}
)
def notify_tool_list_changed() -> None:
"""Notify all connected MCP clients that the tool list changed."""
_send_mcp_notification({
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed",
})
_send_mcp_notification(
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed",
}
)
# Try to import official MCP SDK
try:
@ -510,8 +517,16 @@ PRY_PROMPTS = [
"title": "Research a Company",
"description": "Scrape and analyze a company website for competitive intelligence. Use the pry_scrape and pry_enrich tools to gather data, then summarize findings.",
"arguments": [
{"name": "company_name", "description": "Company name (e.g., 'Anthropic')", "required": True},
{"name": "website", "description": "Company website URL (e.g., 'https://anthropic.com')", "required": True},
{
"name": "company_name",
"description": "Company name (e.g., 'Anthropic')",
"required": True,
},
{
"name": "website",
"description": "Company website URL (e.g., 'https://anthropic.com')",
"required": True,
},
],
},
{
@ -519,8 +534,16 @@ PRY_PROMPTS = [
"title": "Compare Products",
"description": "Scrape product pages from multiple e-commerce sites and create a side-by-side comparison. Use pry_template tool with amazon-product, walmart-product, etc.",
"arguments": [
{"name": "product_query", "description": "What to search for (e.g., 'wireless headphones')", "required": True},
{"name": "sites", "description": "Sites to compare (e.g., ['amazon', 'walmart', 'target'])", "required": True},
{
"name": "product_query",
"description": "What to search for (e.g., 'wireless headphones')",
"required": True,
},
{
"name": "sites",
"description": "Sites to compare (e.g., ['amazon', 'walmart', 'target'])",
"required": True,
},
],
},
{
@ -529,7 +552,11 @@ PRY_PROMPTS = [
"description": "Crawl a website and extract all email addresses, phone numbers, and social media handles using CSS selectors and regex patterns.",
"arguments": [
{"name": "url", "description": "Starting URL", "required": True},
{"name": "max_pages", "description": "Maximum pages to crawl (default: 20)", "required": False},
{
"name": "max_pages",
"description": "Maximum pages to crawl (default: 20)",
"required": False,
},
],
},
{
@ -538,7 +565,11 @@ PRY_PROMPTS = [
"description": "Set up continuous monitoring for a competitor's pricing, product listings, or content. Get notified via webhook on changes.",
"arguments": [
{"name": "url", "description": "Competitor URL to monitor", "required": True},
{"name": "what_to_track", "description": "What to track (e.g., 'product prices', 'blog posts', 'job listings')", "required": True},
{
"name": "what_to_track",
"description": "What to track (e.g., 'product prices', 'blog posts', 'job listings')",
"required": True,
},
{"name": "webhook_url", "description": "Where to send notifications", "required": True},
],
},
@ -548,7 +579,11 @@ PRY_PROMPTS = [
"description": "Scrape an article, summarize key points, and provide structured analysis (sentiment, entities, key claims).",
"arguments": [
{"name": "url", "description": "Article URL", "required": True},
{"name": "focus", "description": "What to focus on (e.g., 'financial implications', 'competitive threats')", "required": False},
{
"name": "focus",
"description": "What to focus on (e.g., 'financial implications', 'competitive threats')",
"required": False,
},
],
},
{
@ -557,7 +592,11 @@ PRY_PROMPTS = [
"description": "Find and extract a specific data table from a webpage. Returns structured CSV/JSON rows.",
"arguments": [
{"name": "url", "description": "Page URL", "required": True},
{"name": "table_description", "description": "Description of the table you want (e.g., 'product pricing table', 'league standings')", "required": True},
{
"name": "table_description",
"description": "Description of the table you want (e.g., 'product pricing table', 'league standings')",
"required": True,
},
],
},
{
@ -566,7 +605,11 @@ PRY_PROMPTS = [
"description": "Scrape any URL with a custom JSON schema. Use pry_extract or pry_scrape with a schema definition.",
"arguments": [
{"name": "url", "description": "URL to scrape", "required": True},
{"name": "fields", "description": "Comma-separated list of fields to extract (e.g., 'title,price,description,image')", "required": True},
{
"name": "fields",
"description": "Comma-separated list of fields to extract (e.g., 'title,price,description,image')",
"required": True,
},
],
},
]
@ -574,7 +617,8 @@ PRY_PROMPTS = [
def make_fallback_server(
base_url: str = DEFAULT_BASE_URL,
tool_executor: Callable[[str, dict[str, Any]], Coroutine[Any, Any, dict[str, Any]]] | None = None,
tool_executor: Callable[[str, dict[str, Any]], Coroutine[Any, Any, dict[str, Any]]]
| None = None,
) -> Any:
"""Build a minimal stdio JSON-RPC server if official MCP SDK not available.
This implements the MCP 2024-11-05 protocol with all required methods."""
@ -583,7 +627,8 @@ def make_fallback_server(
def __init__(
self,
base_url: str = DEFAULT_BASE_URL,
tool_executor: Callable[[str, dict[str, Any]], Coroutine[Any, Any, dict[str, Any]]] | None = None,
tool_executor: Callable[[str, dict[str, Any]], Coroutine[Any, Any, dict[str, Any]]]
| None = None,
) -> None:
self.tools: dict[str, dict[str, Any]] = {}
self.resources: dict[str, dict[str, Any]] = {}
@ -656,7 +701,9 @@ def make_fallback_server(
result = await self.tool_executor(name, arguments)
return self._tool_result_to_content(result, name)
except Exception as e: # noqa: BLE001
logger.warning("mcp_tool_executor_failed", extra={"tool": name, "error": str(e)})
logger.warning(
"mcp_tool_executor_failed", extra={"tool": name, "error": str(e)}
)
return {
"content": [{"type": "text", "text": f"Tool {name} failed: {e}"}],
"isError": True,
@ -744,36 +791,45 @@ def make_fallback_server(
if uri == "pry://catalog":
try:
from template_engine import list_templates
templates = list_templates()
categories: dict[str, list[str]] = {}
for t in templates:
cat = t.get("category", "general")
categories.setdefault(cat, []).append(t.get("template_id", "unknown"))
return json.dumps({
"total_templates": len(templates),
"categories": categories,
"note": "Use pry_search_templates to query dynamically.",
}, indent=2)
return json.dumps(
{
"total_templates": len(templates),
"categories": categories,
"note": "Use pry_search_templates to query dynamically.",
},
indent=2,
)
except (json.JSONDecodeError, ValueError) as e:
return json.dumps({"error": f"Could not load template catalog: {e}"})
if uri == "pry://stats":
return json.dumps({
"server": SERVER_NAME,
"version": SERVER_VERSION,
"protocol_version": MCP_PROTOCOL_VERSION,
"tools_count": len(self.tools),
"resources_count": len(self.resources),
"prompts_count": len(self.prompts),
}, indent=2)
return json.dumps(
{
"server": SERVER_NAME,
"version": SERVER_VERSION,
"protocol_version": MCP_PROTOCOL_VERSION,
"tools_count": len(self.tools),
"resources_count": len(self.resources),
"prompts_count": len(self.prompts),
},
indent=2,
)
if uri == "pry://x402/pricing":
try:
from x402 import X402Handler
return json.dumps(X402Handler().get_stats(), indent=2)
except (json.JSONDecodeError, ValueError) as e:
return json.dumps({"error": f"Could not load x402 pricing: {e}"})
if uri == "pry://referrals":
try:
from referrals import ReferralTracker
return json.dumps(ReferralTracker().get_catalog(), indent=2) # type: ignore[no-untyped-call]
except (json.JSONDecodeError, ValueError) as e:
return json.dumps({"error": f"Could not load referrals: {e}"})
@ -795,110 +851,126 @@ def make_fallback_server(
if name == "research_company":
company = arguments.get("company_name", "the company")
website = arguments.get("website", "")
return [{
"role": "user",
"content": {
"type": "text",
"text": (
f"Research {company}. "
f"{'Start by scraping ' + website + '. ' if website else ''}"
"Use pry_scrape and pry_enrich to gather data, then summarize: "
"what they do, target market, key products, competitive positioning, "
"and any risks or opportunities."
),
},
}]
return [
{
"role": "user",
"content": {
"type": "text",
"text": (
f"Research {company}. "
f"{'Start by scraping ' + website + '. ' if website else ''}"
"Use pry_scrape and pry_enrich to gather data, then summarize: "
"what they do, target market, key products, competitive positioning, "
"and any risks or opportunities."
),
},
}
]
if name == "compare_products":
query = arguments.get("product_query", "the product")
sites = arguments.get("sites", [])
return [{
"role": "user",
"content": {
"type": "text",
"text": (
f"Compare prices and details for '{query}' across {', '.join(sites)}. "
"Use pry_template with amazon-product, walmart-product, etc. "
"Return a side-by-side comparison table."
),
},
}]
return [
{
"role": "user",
"content": {
"type": "text",
"text": (
f"Compare prices and details for '{query}' across {', '.join(sites)}. "
"Use pry_template with amazon-product, walmart-product, etc. "
"Return a side-by-side comparison table."
),
},
}
]
if name == "extract_contacts":
url = arguments.get("url", "")
max_pages = arguments.get("max_pages", 20)
return [{
"role": "user",
"content": {
"type": "text",
"text": (
f"Extract all contact information from {url}. "
f"Crawl up to {max_pages} pages. "
"Use pry_crawl and regex to find emails, phones, and social profiles."
),
},
}]
return [
{
"role": "user",
"content": {
"type": "text",
"text": (
f"Extract all contact information from {url}. "
f"Crawl up to {max_pages} pages. "
"Use pry_crawl and regex to find emails, phones, and social profiles."
),
},
}
]
if name == "monitor_competitor":
url = arguments.get("url", "")
what = arguments.get("what_to_track", "changes")
webhook = arguments.get("webhook_url", "")
return [{
"role": "user",
"content": {
"type": "text",
"text": (
f"Set up monitoring for {url}. Track {what}. "
f"{'Notify via webhook: ' + webhook + '. ' if webhook else ''}"
"Use pry_monitor."
),
},
}]
return [
{
"role": "user",
"content": {
"type": "text",
"text": (
f"Set up monitoring for {url}. Track {what}. "
f"{'Notify via webhook: ' + webhook + '. ' if webhook else ''}"
"Use pry_monitor."
),
},
}
]
if name == "analyze_article":
url = arguments.get("url", "")
focus = arguments.get("focus", "")
return [{
"role": "user",
"content": {
"type": "text",
"text": (
f"Scrape and analyze this article: {url}. "
f"{'Focus on: ' + focus + '. ' if focus else ''}"
"Summarize key points, sentiment, entities, and claims."
),
},
}]
return [
{
"role": "user",
"content": {
"type": "text",
"text": (
f"Scrape and analyze this article: {url}. "
f"{'Focus on: ' + focus + '. ' if focus else ''}"
"Summarize key points, sentiment, entities, and claims."
),
},
}
]
if name == "extract_table":
url = arguments.get("url", "")
desc = arguments.get("table_description", "")
return [{
"role": "user",
"content": {
"type": "text",
"text": (
f"Extract the data table from {url}. Table: {desc}. "
"Use pry_extract with CSS selectors or pry_scrape + structured output."
),
},
}]
return [
{
"role": "user",
"content": {
"type": "text",
"text": (
f"Extract the data table from {url}. Table: {desc}. "
"Use pry_extract with CSS selectors or pry_scrape + structured output."
),
},
}
]
if name == "scrape_with_schema":
url = arguments.get("url", "")
fields = arguments.get("fields", "")
return [{
return [
{
"role": "user",
"content": {
"type": "text",
"text": (
f"Scrape {url} and extract these fields: {fields}. "
"Use pry_extract with a custom JSON schema."
),
},
}
]
arg_text = ", ".join(f"{k}={v}" for k, v in arguments.items())
return [
{
"role": "user",
"content": {
"type": "text",
"text": (
f"Scrape {url} and extract these fields: {fields}. "
"Use pry_extract with a custom JSON schema."
),
"text": f"Execute the {self.prompts[name].get('title', name)} prompt with arguments: {arg_text}",
},
}]
arg_text = ", ".join(f"{k}={v}" for k, v in arguments.items())
return [{
"role": "user",
"content": {
"type": "text",
"text": f"Execute the {self.prompts[name].get('title', name)} prompt with arguments: {arg_text}",
},
}]
}
]
def complete(self, ref: dict[str, Any], argument: dict[str, Any]) -> dict[str, Any]:
"""Provide argument completion suggestions."""
@ -907,11 +979,16 @@ def make_fallback_server(
arg_name = argument.get("name", "")
arg_value = argument.get("value", "")
values: list[str] = []
if ref_type == "ref/prompt" and ref_name == "extract_contacts" and arg_name == "max_pages":
if (
ref_type == "ref/prompt"
and ref_name == "extract_contacts"
and arg_name == "max_pages"
):
values = ["10", "20", "50", "100"]
elif ref_type == "ref/tool" and arg_name == "template_id":
try:
from template_engine import list_templates
templates = list_templates()
values = [
t["template_id"]
@ -963,7 +1040,9 @@ def make_fallback_server(
"result": {"tools": list(self.tools.values())},
}
if method == "tools/call":
tool_result = await self.call_tool(params.get("name", ""), params.get("arguments", {}))
tool_result = await self.call_tool(
params.get("name", ""), params.get("arguments", {})
)
if "error" in tool_result:
return {
"jsonrpc": "2.0",