refactor: convert f-string logging to lazy %-style format (G004)
Sweep all 1676 G004 violations across 112 files, converting
`logger.<level>(f"…{x}…")` to `logger.<level>("…%s…", x)`.
Why: ruff rule G004 was added to pyproject.toml to enforce lazy
%-style logging — defers formatting until the log level is enabled
and lets structured log tooling match the unformatted template.
Conversion preserves rendered output byte-for-byte:
- `{x}` → `%s` + `x`
- `{x!r}` / `{x!s}` / `{x!a}` → `%r` / `%s` / `%a`
- Format specs (`{x:.2f}`, `{x:>10}`) → `%s` + `format(x, 'spec')`
(printf-style specs aren't 1:1 with Python format specs, so we
delegate to `format()` to keep identical output)
- Literal `%` → `%%`
- Concatenated f-strings (`f"a {x} " "b"`) flattened
- Trailing kwargs (`exc_info=True`) preserved
Verified:
- `uv run ruff check --select G004` → 0 violations
- `uv run ty check -- nextcloud_mcp_server` → passes
- `uv run pytest tests/unit/` → 1010 passed
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
a4e6125d28
commit
665cb9b1eb
+112
-85
@@ -169,10 +169,10 @@ def initialize_document_processors():
|
||||
progress_interval=unst_config.get("progress_interval", 10),
|
||||
)
|
||||
registry.register(processor, priority=10)
|
||||
logger.info(f"Registered Unstructured processor: {unst_config['api_url']}")
|
||||
logger.info("Registered Unstructured processor: %s", unst_config["api_url"])
|
||||
registered_count += 1
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to register Unstructured processor: {e}")
|
||||
logger.warning("Failed to register Unstructured processor: %s", e)
|
||||
|
||||
# Register Tesseract processor
|
||||
if "tesseract" in config["processors"]:
|
||||
@@ -187,10 +187,10 @@ def initialize_document_processors():
|
||||
default_lang=tess_config["lang"],
|
||||
)
|
||||
registry.register(processor, priority=5)
|
||||
logger.info(f"Registered Tesseract processor: lang={tess_config['lang']}")
|
||||
logger.info("Registered Tesseract processor: lang=%s", tess_config["lang"])
|
||||
registered_count += 1
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to register Tesseract processor: {e}")
|
||||
logger.warning("Failed to register Tesseract processor: %s", e)
|
||||
|
||||
# Register PyMuPDF processor (high priority, local, no API required)
|
||||
if "pymupdf" in config["processors"]:
|
||||
@@ -206,11 +206,12 @@ def initialize_document_processors():
|
||||
)
|
||||
registry.register(processor, priority=15) # Higher than unstructured
|
||||
logger.info(
|
||||
f"Registered PyMuPDF processor: extract_images={pymupdf_config.get('extract_images', True)}"
|
||||
"Registered PyMuPDF processor: extract_images=%s",
|
||||
pymupdf_config.get("extract_images", True),
|
||||
)
|
||||
registered_count += 1
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to register PyMuPDF processor: {e}")
|
||||
logger.warning("Failed to register PyMuPDF processor: %s", e)
|
||||
|
||||
# Register custom processor
|
||||
if "custom" in config["processors"]:
|
||||
@@ -229,16 +230,19 @@ def initialize_document_processors():
|
||||
)
|
||||
registry.register(processor, priority=1)
|
||||
logger.info(
|
||||
f"Registered Custom processor '{custom_config['name']}': {custom_config['api_url']}"
|
||||
"Registered Custom processor '%s': %s",
|
||||
custom_config["name"],
|
||||
custom_config["api_url"],
|
||||
)
|
||||
registered_count += 1
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to register Custom processor: {e}")
|
||||
logger.warning("Failed to register Custom processor: %s", e)
|
||||
|
||||
if registered_count > 0:
|
||||
logger.info(
|
||||
f"Document processing initialized with {registered_count} processor(s): "
|
||||
f"{', '.join(registry.list_processors())}"
|
||||
"Document processing initialized with %s processor(s): %s",
|
||||
registered_count,
|
||||
", ".join(registry.list_processors()),
|
||||
)
|
||||
else:
|
||||
logger.warning("Document processing enabled but no processors registered")
|
||||
@@ -409,10 +413,10 @@ class BasicAuthMiddleware:
|
||||
"password": password,
|
||||
}
|
||||
logger.debug(
|
||||
f"BasicAuth credentials extracted for user: {username}"
|
||||
"BasicAuth credentials extracted for user: %s", username
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to extract BasicAuth credentials: {e}")
|
||||
logger.warning("Failed to extract BasicAuth credentials: %s", e)
|
||||
|
||||
await self.app(scope, receive, send)
|
||||
|
||||
@@ -451,7 +455,7 @@ async def load_oauth_client_credentials(
|
||||
client_data = await storage.get_oauth_client()
|
||||
if client_data:
|
||||
logger.info(
|
||||
f"Loaded OAuth client from SQLite: {client_data['client_id'][:16]}..."
|
||||
"Loaded OAuth client from SQLite: %s...", client_data["client_id"][:16]
|
||||
)
|
||||
return (client_data["client_id"], client_data["client_secret"])
|
||||
except ValueError:
|
||||
@@ -496,7 +500,7 @@ async def load_oauth_client_credentials(
|
||||
dcr_scopes = f"{dcr_scopes} offline_access"
|
||||
logger.info("✓ offline_access scope enabled for refresh tokens")
|
||||
|
||||
logger.info(f"MCP server DCR scopes (resource server): {dcr_scopes}")
|
||||
logger.info("MCP server DCR scopes (resource server): %s", dcr_scopes)
|
||||
|
||||
# Get token type from environment (Bearer or jwt)
|
||||
# Note: Must be lowercase "jwt" to match OIDC app's check
|
||||
@@ -504,7 +508,7 @@ async def load_oauth_client_credentials(
|
||||
# Special case: "bearer" should remain capitalized for compatibility
|
||||
if token_type != "jwt":
|
||||
token_type = "Bearer"
|
||||
logger.info(f"Requesting token type: {token_type}")
|
||||
logger.info("Requesting token type: %s", token_type)
|
||||
|
||||
# Ensure OAuth client in SQLite storage
|
||||
storage = RefreshTokenStorage.from_env()
|
||||
@@ -525,7 +529,7 @@ async def load_oauth_client_credentials(
|
||||
resource_url=resource_url, # RFC 9728 Protected Resource URL
|
||||
)
|
||||
|
||||
logger.info(f"OAuth client ready: {client_info.client_id[:16]}...")
|
||||
logger.info("OAuth client ready: %s...", client_info.client_id[:16])
|
||||
return (client_info.client_id, client_info.client_secret)
|
||||
|
||||
# No credentials available
|
||||
@@ -555,7 +559,8 @@ async def app_lifespan_basic(server: FastMCP) -> AsyncIterator[AppContext]:
|
||||
is_multi_user = settings.enable_multi_user_basic_auth
|
||||
|
||||
logger.info(
|
||||
f"Starting MCP session in {'multi-user' if is_multi_user else 'single-user'} BasicAuth mode"
|
||||
"Starting MCP session in %s BasicAuth mode",
|
||||
"multi-user" if is_multi_user else "single-user",
|
||||
)
|
||||
|
||||
# Only create shared client for single-user mode
|
||||
@@ -633,7 +638,7 @@ async def setup_oauth_config():
|
||||
discovery_url = os.getenv(
|
||||
"OIDC_DISCOVERY_URL", f"{nextcloud_host}/.well-known/openid-configuration"
|
||||
)
|
||||
logger.info(f"Performing OIDC discovery: {discovery_url}")
|
||||
logger.info("Performing OIDC discovery: %s", discovery_url)
|
||||
|
||||
# Perform OIDC discovery
|
||||
async with nextcloud_httpx_client(follow_redirects=True) as client:
|
||||
@@ -654,12 +659,12 @@ async def setup_oauth_config():
|
||||
registration_endpoint = discovery.get("registration_endpoint")
|
||||
|
||||
logger.info("OIDC endpoints discovered:")
|
||||
logger.info(f" Issuer: {issuer}")
|
||||
logger.info(f" Userinfo: {userinfo_uri}")
|
||||
logger.info(" Issuer: %s", issuer)
|
||||
logger.info(" Userinfo: %s", userinfo_uri)
|
||||
if jwks_uri:
|
||||
logger.info(f" JWKS: {jwks_uri}")
|
||||
logger.info(" JWKS: %s", jwks_uri)
|
||||
if introspection_uri:
|
||||
logger.info(f" Introspection: {introspection_uri}")
|
||||
logger.info(" Introspection: %s", introspection_uri)
|
||||
|
||||
# Auto-detect provider mode based on issuer
|
||||
# External IdP mode: issuer doesn't match Nextcloud host
|
||||
@@ -685,7 +690,9 @@ async def setup_oauth_config():
|
||||
if is_external_idp:
|
||||
oauth_provider = "external" # Could be Keycloak, Auth0, Okta, etc.
|
||||
logger.info(
|
||||
f"✓ Detected external IdP mode (issuer: {issuer} != Nextcloud: {nextcloud_host})"
|
||||
"✓ Detected external IdP mode (issuer: %s != Nextcloud: %s)",
|
||||
issuer,
|
||||
nextcloud_host,
|
||||
)
|
||||
logger.info(" Tokens will be validated via Nextcloud user_oidc app")
|
||||
else:
|
||||
@@ -716,7 +723,7 @@ async def setup_oauth_config():
|
||||
"✓ Refresh token storage initialized (offline_access enabled)"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize refresh token storage: {e}")
|
||||
logger.error("Failed to initialize refresh token storage: %s", e)
|
||||
logger.warning(
|
||||
"Continuing without refresh token storage - users will need to re-authenticate after token expiration"
|
||||
)
|
||||
@@ -726,7 +733,7 @@ async def setup_oauth_config():
|
||||
client_secret = os.getenv("NEXTCLOUD_OIDC_CLIENT_SECRET")
|
||||
|
||||
if client_id and client_secret:
|
||||
logger.info(f"Using static OIDC client credentials: {client_id}")
|
||||
logger.info("Using static OIDC client credentials: %s", client_id)
|
||||
elif registration_endpoint:
|
||||
logger.info(
|
||||
"NEXTCLOUD_OIDC_CLIENT_ID not set, attempting Dynamic Client Registration"
|
||||
@@ -753,13 +760,13 @@ async def setup_oauth_config():
|
||||
# Warn if resource URIs are not configured (required for ADR-005 compliance)
|
||||
if not os.getenv("NEXTCLOUD_MCP_SERVER_URL"):
|
||||
logger.warning(
|
||||
f"NEXTCLOUD_MCP_SERVER_URL not set, defaulting to: {mcp_server_url}. "
|
||||
"This should be set explicitly for proper audience validation."
|
||||
"NEXTCLOUD_MCP_SERVER_URL not set, defaulting to: %s. This should be set explicitly for proper audience validation.",
|
||||
mcp_server_url,
|
||||
)
|
||||
if not os.getenv("NEXTCLOUD_RESOURCE_URI"):
|
||||
logger.warning(
|
||||
f"NEXTCLOUD_RESOURCE_URI not set, defaulting to: {nextcloud_resource_uri}. "
|
||||
"This should be set explicitly for proper audience validation."
|
||||
"NEXTCLOUD_RESOURCE_URI not set, defaulting to: %s. This should be set explicitly for proper audience validation.",
|
||||
nextcloud_resource_uri,
|
||||
)
|
||||
|
||||
# Create settings for UnifiedTokenVerifier (use same settings instance from start of function)
|
||||
@@ -790,8 +797,8 @@ async def setup_oauth_config():
|
||||
logger.info(
|
||||
"✓ Multi-audience mode enabled (ADR-005) - tokens must contain both MCP and Nextcloud audiences"
|
||||
)
|
||||
logger.info(f" Required MCP audience: {client_id} or {mcp_server_url}")
|
||||
logger.info(f" Required Nextcloud audience: {nextcloud_resource_uri}")
|
||||
logger.info(" Required MCP audience: %s or %s", client_id, mcp_server_url)
|
||||
logger.info(" Required Nextcloud audience: %s", nextcloud_resource_uri)
|
||||
|
||||
if introspection_uri:
|
||||
logger.info("✓ Opaque token introspection enabled (RFC 7662)")
|
||||
@@ -881,7 +888,8 @@ async def setup_oauth_config_for_multi_user_basic(
|
||||
f"{nextcloud_host}/.well-known/openid-configuration",
|
||||
)
|
||||
logger.info(
|
||||
f"Performing OIDC discovery for multi-user BasicAuth hybrid mode: {discovery_url}"
|
||||
"Performing OIDC discovery for multi-user BasicAuth hybrid mode: %s",
|
||||
discovery_url,
|
||||
)
|
||||
|
||||
# Perform OIDC discovery
|
||||
@@ -894,20 +902,22 @@ async def setup_oauth_config_for_multi_user_basic(
|
||||
discovery = response.json()
|
||||
except httpx.HTTPStatusError as e:
|
||||
logger.error(
|
||||
f"OIDC discovery failed: HTTP {e.response.status_code} from {discovery_url}"
|
||||
"OIDC discovery failed: HTTP %s from %s",
|
||||
e.response.status_code,
|
||||
discovery_url,
|
||||
)
|
||||
raise ValueError(
|
||||
f"OIDC discovery failed: HTTP {e.response.status_code} from {discovery_url}. "
|
||||
"Ensure Nextcloud OIDC (user_oidc app) is installed and configured."
|
||||
) from e
|
||||
except httpx.RequestError as e:
|
||||
logger.error(f"OIDC discovery failed: {e}")
|
||||
logger.error("OIDC discovery failed: %s", e)
|
||||
raise ValueError(
|
||||
f"OIDC discovery failed: Cannot connect to {discovery_url}. Error: {e}"
|
||||
) from e
|
||||
except (KeyError, ValueError) as e:
|
||||
logger.error(
|
||||
f"OIDC discovery failed: Invalid response from {discovery_url}: {e}"
|
||||
"OIDC discovery failed: Invalid response from %s: %s", discovery_url, e
|
||||
)
|
||||
raise ValueError(
|
||||
f"OIDC discovery failed: Invalid response from {discovery_url}. "
|
||||
@@ -923,10 +933,10 @@ async def setup_oauth_config_for_multi_user_basic(
|
||||
introspection_uri = discovery.get("introspection_endpoint")
|
||||
|
||||
logger.info("OIDC endpoints configured for management API:")
|
||||
logger.info(f" Issuer: {issuer}")
|
||||
logger.info(f" Userinfo: {userinfo_uri}")
|
||||
logger.info(f" JWKS: {jwks_uri}")
|
||||
logger.info(f" Introspection: {introspection_uri}")
|
||||
logger.info(" Issuer: %s", issuer)
|
||||
logger.info(" Userinfo: %s", userinfo_uri)
|
||||
logger.info(" JWKS: %s", jwks_uri)
|
||||
logger.info(" Introspection: %s", introspection_uri)
|
||||
|
||||
# Get MCP server URL for audience validation
|
||||
mcp_server_url = os.getenv("NEXTCLOUD_MCP_SERVER_URL", "http://localhost:8000")
|
||||
@@ -982,8 +992,8 @@ async def setup_oauth_config_for_multi_user_basic(
|
||||
"✓ Refresh token storage initialized for background operations (hybrid mode)"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize refresh token storage: {e}")
|
||||
logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
||||
logger.error("Failed to initialize refresh token storage: %s", e)
|
||||
logger.debug("Full traceback:\\n%s", traceback.format_exc())
|
||||
logger.warning(
|
||||
"Continuing without refresh token storage - webhook management may be limited"
|
||||
)
|
||||
@@ -1012,8 +1022,8 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
logger.error(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
|
||||
logger.info(f"✅ Configuration validated successfully for {mode.value} mode")
|
||||
logger.debug(f"Mode details:\n{get_mode_summary(mode)}")
|
||||
logger.info("✅ Configuration validated successfully for %s mode", mode.value)
|
||||
logger.debug("Mode details:\\n%s", get_mode_summary(mode))
|
||||
|
||||
# Derive helper variables for backward compatibility with existing code.
|
||||
# `oauth_enabled` is True for the LOGIN_FLOW (formerly OAUTH_SINGLE_AUDIENCE)
|
||||
@@ -1033,7 +1043,7 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
if settings.metrics_enabled:
|
||||
setup_metrics(port=settings.metrics_port)
|
||||
logger.info(
|
||||
f"Prometheus metrics enabled on dedicated port {settings.metrics_port}"
|
||||
"Prometheus metrics enabled on dedicated port %s", settings.metrics_port
|
||||
)
|
||||
|
||||
# Setup OpenTelemetry tracing (optional)
|
||||
@@ -1045,7 +1055,8 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
sampling_rate=settings.otel_traces_sampler_arg,
|
||||
)
|
||||
logger.info(
|
||||
f"OpenTelemetry tracing enabled (endpoint: {settings.otel_exporter_otlp_endpoint})"
|
||||
"OpenTelemetry tracing enabled (endpoint: %s)",
|
||||
settings.otel_exporter_otlp_endpoint,
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
@@ -1093,7 +1104,8 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
# This avoids relying on discovery doc which may use public URLs unreachable from containers
|
||||
registration_endpoint = f"{settings.nextcloud_host}/apps/oidc/register"
|
||||
logger.info(
|
||||
f"Attempting Dynamic Client Registration at: {registration_endpoint}"
|
||||
"Attempting Dynamic Client Registration at: %s",
|
||||
registration_endpoint,
|
||||
)
|
||||
|
||||
# Perform DCR
|
||||
@@ -1108,13 +1120,13 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
registration_endpoint=registration_endpoint,
|
||||
)
|
||||
logger.info(
|
||||
f"✓ Dynamic Client Registration successful for background operations "
|
||||
f"(client_id: {client_id[:16]}...)"
|
||||
"✓ Dynamic Client Registration successful for background operations (client_id: %s...)",
|
||||
client_id[:16],
|
||||
)
|
||||
return (client_id, client_secret)
|
||||
except Exception as e:
|
||||
logger.error(f"Dynamic Client Registration failed: {e}")
|
||||
logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
||||
logger.error("Dynamic Client Registration failed: %s", e)
|
||||
logger.debug("Full traceback:\\n%s", traceback.format_exc())
|
||||
logger.warning("Background vector sync will be disabled.")
|
||||
return None
|
||||
|
||||
@@ -1151,8 +1163,8 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
# - httpx.HTTPError: Network issues, OIDC discovery failures
|
||||
# - ValueError: Missing required configuration (NEXTCLOUD_HOST)
|
||||
# - KeyError: Missing required fields in OIDC discovery response
|
||||
logger.error(f"Failed to setup OAuth infrastructure: {e}")
|
||||
logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
||||
logger.error("Failed to setup OAuth infrastructure: %s", e)
|
||||
logger.debug("Full traceback:\\n%s", traceback.format_exc())
|
||||
logger.warning(
|
||||
"Management API will be unavailable. "
|
||||
"Webhook management from Astrolabe admin UI will not work."
|
||||
@@ -1163,8 +1175,8 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
except Exception as e:
|
||||
# Unexpected error - this is a programming error, re-raise it
|
||||
logger.error(
|
||||
f"Unexpected error during OAuth infrastructure setup: {e}. "
|
||||
"This is likely a programming error that should be fixed."
|
||||
"Unexpected error during OAuth infrastructure setup: %s. This is likely a programming error that should be fixed.",
|
||||
e,
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -1191,7 +1203,7 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
Lifespan context for OAuth mode - captures OAuth configuration from outer scope.
|
||||
"""
|
||||
logger.info("Starting MCP server in OAuth mode")
|
||||
logger.info(f"Using OAuth provider: {oauth_provider}")
|
||||
logger.info("Using OAuth provider: %s", oauth_provider)
|
||||
if refresh_token_storage:
|
||||
logger.info("Refresh token storage is available")
|
||||
if oauth_client:
|
||||
@@ -1223,7 +1235,7 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
try:
|
||||
await oauth_client.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"Error closing OAuth client: {e}")
|
||||
logger.warning("Error closing OAuth client: %s", e)
|
||||
logger.info("MCP server shutdown complete")
|
||||
|
||||
mcp = FastMCP(
|
||||
@@ -1239,7 +1251,7 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
)
|
||||
else:
|
||||
# BasicAuth modes (single-user or multi-user)
|
||||
logger.info(f"Configuring MCP server for {mode.value} mode")
|
||||
logger.info("Configuring MCP server for %s mode", mode.value)
|
||||
mcp = FastMCP(
|
||||
"Nextcloud MCP",
|
||||
lifespan=app_lifespan_basic,
|
||||
@@ -1264,11 +1276,13 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
# Configure only the enabled apps
|
||||
for app_name in enabled_apps:
|
||||
if app_name in AVAILABLE_APPS:
|
||||
logger.info(f"Configuring {app_name} tools")
|
||||
logger.info("Configuring %s tools", app_name)
|
||||
AVAILABLE_APPS[app_name](mcp)
|
||||
else:
|
||||
logger.warning(
|
||||
f"Unknown app: {app_name}. Available apps: {list(AVAILABLE_APPS.keys())}"
|
||||
"Unknown app: %s. Available apps: %s",
|
||||
app_name,
|
||||
list(AVAILABLE_APPS.keys()),
|
||||
)
|
||||
|
||||
# Register semantic search tools (cross-app feature)
|
||||
@@ -1304,8 +1318,9 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
user_scopes = get_access_token_scopes()
|
||||
is_jwt = is_jwt_token()
|
||||
logger.info(
|
||||
f"🔍 list_tools called - Token type: {'JWT' if is_jwt else 'opaque/none'}, "
|
||||
f"User scopes: {user_scopes}"
|
||||
"🔍 list_tools called - Token type: %s, User scopes: %s",
|
||||
"JWT" if is_jwt else "opaque/none",
|
||||
user_scopes,
|
||||
)
|
||||
|
||||
# Get all tools
|
||||
@@ -1323,14 +1338,17 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
]
|
||||
token_type = "JWT" if is_jwt else "Bearer"
|
||||
logger.info(
|
||||
f"✂️ {token_type} scope filtering: {len(allowed_tools)}/{len(all_tools)} tools "
|
||||
f"available for scopes: {user_scopes}"
|
||||
"✂️ %s scope filtering: %s/%s tools available for scopes: %s",
|
||||
token_type,
|
||||
len(allowed_tools),
|
||||
len(all_tools),
|
||||
user_scopes,
|
||||
)
|
||||
else:
|
||||
# BasicAuth mode or no token - show all tools
|
||||
allowed_tools = all_tools
|
||||
logger.info(
|
||||
f"📋 Showing all {len(all_tools)} tools (no token/BasicAuth)"
|
||||
"📋 Showing all %s tools (no token/BasicAuth)", len(all_tools)
|
||||
)
|
||||
|
||||
# Return the Tool objects directly (they're already in the correct format)
|
||||
@@ -1457,7 +1475,8 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
break
|
||||
|
||||
logger.info(
|
||||
f"OAuth context initialized for login routes (client_id={client_id[:16]}...)"
|
||||
"OAuth context initialized for login routes (client_id=%s...)",
|
||||
client_id[:16],
|
||||
)
|
||||
else:
|
||||
# BasicAuth mode - initialize storage for webhook management
|
||||
@@ -1505,7 +1524,8 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
}
|
||||
app.state.oauth_context = oauth_context_dict
|
||||
logger.info(
|
||||
f"✓ OAuth context initialized for management APIs (hybrid mode, client_id={sync_client_id[:16]}...)"
|
||||
"✓ OAuth context initialized for management APIs (hybrid mode, client_id=%s...)",
|
||||
sync_client_id[:16],
|
||||
)
|
||||
elif multi_user_basic_oauth_creds and multi_user_token_verifier is None:
|
||||
logger.warning(
|
||||
@@ -1573,7 +1593,7 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
await get_qdrant_client() # Triggers collection creation if needed
|
||||
logger.info("Qdrant collection ready")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize Qdrant collection: {e}")
|
||||
logger.error("Failed to initialize Qdrant collection: %s", e)
|
||||
raise RuntimeError(
|
||||
f"Cannot start vector sync - Qdrant initialization failed: {e}"
|
||||
) from e
|
||||
@@ -1639,8 +1659,8 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
_vector_sync_state.eviction_task_group = tg
|
||||
|
||||
logger.info(
|
||||
f"Background sync tasks started: 1 scanner + "
|
||||
f"{settings.vector_sync_processor_workers} processors"
|
||||
"Background sync tasks started: 1 scanner + %s processors",
|
||||
settings.vector_sync_processor_workers,
|
||||
)
|
||||
|
||||
# Run MCP session manager and yield
|
||||
@@ -1664,7 +1684,7 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
# OAuth mode with background operations - multi-user sync
|
||||
# Also used for multi-user BasicAuth mode (client auth is BasicAuth, background sync uses app passwords or OAuth)
|
||||
mode_desc = "OAuth mode" if oauth_enabled else "Multi-user BasicAuth mode"
|
||||
logger.info(f"Starting background vector sync tasks for {mode_desc}")
|
||||
logger.info("Starting background vector sync tasks for %s", mode_desc)
|
||||
|
||||
# Get nextcloud_host (from settings - already validated)
|
||||
nextcloud_host_for_sync = settings.nextcloud_host
|
||||
@@ -1735,7 +1755,7 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
await get_qdrant_client() # Triggers collection creation if needed
|
||||
logger.info("Qdrant collection ready")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize Qdrant collection: {e}")
|
||||
logger.error("Failed to initialize Qdrant collection: %s", e)
|
||||
raise RuntimeError(
|
||||
f"Cannot start vector sync - Qdrant initialization failed: {e}"
|
||||
) from e
|
||||
@@ -1748,10 +1768,12 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
)
|
||||
if removed:
|
||||
logger.info(
|
||||
f"Cleaned up {len(removed)} stale app password(s): {removed}"
|
||||
"Cleaned up %s stale app password(s): %s",
|
||||
len(removed),
|
||||
removed,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"App password cleanup failed (non-fatal): {e}")
|
||||
logger.warning("App password cleanup failed (non-fatal): %s", e)
|
||||
|
||||
# Initialize shared state
|
||||
send_stream, receive_stream = anyio.create_memory_object_stream(
|
||||
@@ -1829,8 +1851,8 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
_vector_sync_state.eviction_task_group = tg
|
||||
|
||||
logger.info(
|
||||
f"Background sync tasks started: 1 user manager + "
|
||||
f"{settings.vector_sync_processor_workers} processors"
|
||||
"Background sync tasks started: 1 user manager + %s processors",
|
||||
settings.vector_sync_processor_workers,
|
||||
)
|
||||
|
||||
# Run MCP session manager and yield
|
||||
@@ -2137,8 +2159,10 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
|
||||
if oauth_provisioning_available:
|
||||
logger.info(
|
||||
f"OAuth provisioning routes enabled for mode: {mode.value} "
|
||||
f"(oauth_enabled={oauth_enabled}, hybrid_mode={not oauth_enabled})"
|
||||
"OAuth provisioning routes enabled for mode: %s (oauth_enabled=%s, hybrid_mode=%s)",
|
||||
mode.value,
|
||||
oauth_enabled,
|
||||
not oauth_enabled,
|
||||
)
|
||||
|
||||
def oauth_protected_resource_metadata(request):
|
||||
@@ -2330,7 +2354,7 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
browser_routes.append(
|
||||
Mount("/static", StaticFiles(directory=static_dir), name="static")
|
||||
)
|
||||
logger.info(f"Mounted static files from {static_dir}")
|
||||
logger.info("Mounted static files from %s", static_dir)
|
||||
|
||||
browser_app = Starlette(routes=browser_routes)
|
||||
browser_app.add_middleware(
|
||||
@@ -2381,13 +2405,14 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
token_preview = (
|
||||
auth_header[:50] + "..." if len(auth_header) > 50 else auth_header
|
||||
)
|
||||
logger.info(f"🔑 /mcp request with Authorization: {token_preview}")
|
||||
logger.info("🔑 /mcp request with Authorization: %s", token_preview)
|
||||
else:
|
||||
# Only warn about missing Authorization in OAuth mode
|
||||
# In BasicAuth mode, /mcp requests without Authorization are expected
|
||||
if oauth_enabled:
|
||||
logger.warning(
|
||||
f"⚠️ /mcp request WITHOUT Authorization header from {request.client}"
|
||||
"⚠️ /mcp request WITHOUT Authorization header from %s",
|
||||
request.client,
|
||||
)
|
||||
|
||||
# Log client capabilities on initialize request
|
||||
@@ -2404,8 +2429,9 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
client_info = params.get("clientInfo", {})
|
||||
|
||||
logger.info(
|
||||
f"🔌 MCP client connected: {client_info.get('name', 'unknown')} "
|
||||
f"v{client_info.get('version', 'unknown')}"
|
||||
"🔌 MCP client connected: %s v%s",
|
||||
client_info.get("name", "unknown"),
|
||||
client_info.get("version", "unknown"),
|
||||
)
|
||||
|
||||
# Log capabilities in a structured way
|
||||
@@ -2421,16 +2447,17 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"📋 Client capabilities: {', '.join(cap_summary) if cap_summary else 'none'}"
|
||||
"📋 Client capabilities: %s",
|
||||
", ".join(cap_summary) if cap_summary else "none",
|
||||
)
|
||||
# Log full capabilities at INFO level to diagnose capability issues
|
||||
logger.info(
|
||||
f"Full capabilities JSON: {json.dumps(capabilities)}"
|
||||
"Full capabilities JSON: %s", json.dumps(capabilities)
|
||||
)
|
||||
except Exception as e:
|
||||
# Don't fail the request if logging fails
|
||||
logger.debug(
|
||||
f"Failed to parse MCP request for capability logging: {e}"
|
||||
"Failed to parse MCP request for capability logging: %s", e
|
||||
)
|
||||
|
||||
response = await call_next(request)
|
||||
|
||||
Reference in New Issue
Block a user