Skip to content

Commit 65acf41

Browse files
evantahlercursoragentCopilotEricGustin
authored
Add startup warnings for missing secrets (#712)
Add startup warnings for missing tool secrets to provide faster feedback on configuration issues. --- Linear Issue: [TOO-198](https://linear.app/arcadedev/issue/TOO-198/add-startup-warnings-for-missing-tool-secrets) <a href="https://cursor.com/background-agent?bcId=bc-203d1b6a-80a7-4933-b3ff-b3a9220b5809"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-cursor-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-cursor-light.svg"><img alt="Open in Cursor" src="https://cursor.com/open-in-cursor.svg"></picture></a>&nbsp;<a href="https://cursor.com/agents?id=bc-203d1b6a-80a7-4933-b3ff-b3a9220b5809"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-web-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-web-light.svg"><img alt="Open in Web" src="https://cursor.com/open-in-web.svg"></picture></a> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Eric Gustin <eric@arcade.dev>
1 parent f887877 commit 65acf41

6 files changed

Lines changed: 438 additions & 23 deletions

File tree

libs/arcade-mcp-server/arcade_mcp_server/__main__.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"""
2121

2222
import asyncio
23-
import logging
2423
import os
2524
import sys
2625
from pathlib import Path
@@ -32,23 +31,11 @@
3231
from dotenv import load_dotenv
3332
from loguru import logger
3433

34+
from arcade_mcp_server.logging_utils import intercept_standard_logging
3535
from arcade_mcp_server.server import MCPServer
3636
from arcade_mcp_server.settings import MCPSettings
3737

3838

39-
# Logging setup with Loguru
40-
class LoguruInterceptHandler(logging.Handler):
41-
"""Intercept standard logging and route to Loguru."""
42-
43-
def emit(self, record: logging.LogRecord) -> None:
44-
try:
45-
level = logger.level(record.levelname).name
46-
except ValueError:
47-
level = str(record.levelno)
48-
49-
logger.opt(exception=record.exc_info).log(level, record.getMessage())
50-
51-
5239
def setup_logging(level: str = "INFO", stdio_mode: bool = False) -> None:
5340
"""Configure logging with Loguru."""
5441
# Remove existing handlers
@@ -74,7 +61,7 @@ def setup_logging(level: str = "INFO", stdio_mode: bool = False) -> None:
7461
)
7562

7663
# Intercept standard logging
77-
logging.basicConfig(handlers=[LoguruInterceptHandler()], level=0, force=True)
64+
intercept_standard_logging()
7865

7966

8067
def initialize_tool_catalog(
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Shared logging utilities for MCP server."""
2+
3+
import logging
4+
5+
from loguru import logger
6+
7+
8+
class LoguruInterceptHandler(logging.Handler):
9+
"""Intercept standard logging and route to Loguru.
10+
11+
This handler bridges the standard Python logging module with Loguru,
12+
ensuring all logs (from both systems) use the same formatting.
13+
"""
14+
15+
def emit(self, record: logging.LogRecord) -> None:
16+
try:
17+
level = logger.level(record.levelname).name
18+
except ValueError:
19+
level = str(record.levelno)
20+
21+
logger.opt(exception=record.exc_info).log(level, record.getMessage())
22+
23+
24+
def intercept_standard_logging() -> None:
25+
"""Configure standard logging to route through Loguru.
26+
27+
This should be called after Loguru is configured to ensure all
28+
standard logging calls are intercepted and formatted consistently.
29+
"""
30+
logging.basicConfig(handlers=[LoguruInterceptHandler()], level=0, force=True)

libs/arcade-mcp-server/arcade_mcp_server/mcp_app.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from watchfiles import watch
2525

2626
from arcade_mcp_server.exceptions import ServerError
27+
from arcade_mcp_server.logging_utils import intercept_standard_logging
2728
from arcade_mcp_server.server import MCPServer
2829
from arcade_mcp_server.settings import MCPSettings, ServerSettings
2930
from arcade_mcp_server.types import Prompt, PromptMessage, Resource
@@ -217,6 +218,9 @@ def _setup_logging(self, stdio_mode: bool = False) -> None:
217218
diagnose=(self.log_level == "DEBUG"),
218219
)
219220

221+
# Intercept standard logging and route through Loguru
222+
intercept_standard_logging()
223+
220224
def add_tool(
221225
self,
222226
func: Callable[P, T],

libs/arcade-mcp-server/arcade_mcp_server/server.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ async def _start(self) -> None:
307307
await self._tool_manager.load_from_catalog(self._initial_catalog)
308308
except Exception:
309309
logger.exception("Failed to load tools from initial catalog")
310+
311+
# Check for missing secrets and log warnings (only when worker routes are disabled)
312+
await self._check_and_warn_missing_secrets()
313+
310314
await self._resource_manager.start()
311315
await self._prompt_manager.start()
312316
await self.lifespan_manager.startup()
@@ -654,6 +658,40 @@ async def _create_tool_context(
654658

655659
return tool_context
656660

661+
async def _check_and_warn_missing_secrets(self) -> None:
662+
"""
663+
Check for missing tool secrets and log warnings.
664+
665+
This method is called during server startup to provide early feedback
666+
about missing configuration. It only runs when worker routes are disabled
667+
(when ARCADE_WORKER_SECRET is not set), as worker routes receive secrets
668+
with tool execution information.
669+
"""
670+
# Skip validation if worker routes are enabled
671+
if self.settings.arcade.server_secret:
672+
logger.debug("Skipping secret validation check - worker routes are enabled")
673+
return
674+
675+
# Get all available secrets from settings and environment
676+
available_secrets = set(self.settings.tool_secrets().keys()) | set(os.environ.keys())
677+
678+
# Check each tool for missing secrets
679+
managed_tools = await self._tool_manager.registry.list()
680+
for managed_tool in managed_tools:
681+
tool = managed_tool["materialized"]
682+
if tool.definition.requirements and tool.definition.requirements.secrets:
683+
missing_secrets = []
684+
for secret_requirement in tool.definition.requirements.secrets:
685+
if secret_requirement.key not in available_secrets:
686+
missing_secrets.append(secret_requirement.key)
687+
688+
if missing_secrets:
689+
secret_list = "', '".join(missing_secrets)
690+
tool_name = tool.definition.name
691+
logger.warning(
692+
f"Tool '{tool_name}' declares secret(s) '{secret_list}' which is/are not set. It will return an error if called."
693+
)
694+
657695
async def _handle_call_tool(
658696
self,
659697
message: CallToolRequest,

libs/arcade-mcp-server/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "arcade-mcp-server"
7-
version = "1.10.3"
7+
version = "1.11.0"
88
description = "Model Context Protocol (MCP) server framework for Arcade.dev"
99
readme = "README.md"
1010
authors = [{ name = "Arcade.dev" }]

0 commit comments

Comments
 (0)