feat: log the available MCP tools at debug level

With LOG_LEVEL=DEBUG, the generated tool list (name and first line of
description) is logged at startup for both transports. The listing is
skipped entirely at higher log levels.
This commit is contained in:
2026-10-19 07:17:33 +00:00
parent 363a1bee69
commit 6255e71c50
2 changed files with 19 additions and 1 deletions
+2 -1
View File
@@ -87,7 +87,8 @@ OPENAPI_SPEC_URL=https://petstore3.swagger.io/api/v3/openapi.json uv run xmcp
Authentication examples: for a bearer token leave the defaults; for an API key Authentication examples: for a bearer token leave the defaults; for an API key
header use `API_AUTH_HEADER=X-API-Key` with `API_AUTH_SCHEME` set to an empty header use `API_AUTH_HEADER=X-API-Key` with `API_AUTH_SCHEME` set to an empty
string. string. With `LOG_LEVEL=DEBUG`, the list of generated MCP tools (name and
description) is logged at startup.
### Per-client API keys (HTTP transport) ### Per-client API keys (HTTP transport)
+17
View File
@@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import logging import logging
import os import os
@@ -53,6 +54,19 @@ def build_server(config: Config) -> FastMCP:
return server return server
async def _log_available_tools(server: FastMCP) -> None:
"""Log the tools generated from the OpenAPI document (DEBUG level)."""
tools = await server.list_tools()
unique: dict[str, object] = {}
for tool in tools:
unique.setdefault(tool.name, tool)
logger.debug("Available MCP tools (%d):", len(unique))
for tool in unique.values():
description = (tool.description or "").strip().splitlines()
suffix = f" — {description[0]}" if description else ""
logger.debug(" - %s%s", tool.name, suffix)
def main() -> None: def main() -> None:
try: try:
config = Config.from_env() config = Config.from_env()
@@ -71,6 +85,9 @@ def main() -> None:
except SpecError as exc: except SpecError as exc:
raise SystemExit(f"Failed to load the OpenAPI document: {exc}") from exc raise SystemExit(f"Failed to load the OpenAPI document: {exc}") from exc
if logger.isEnabledFor(logging.DEBUG):
asyncio.run(_log_available_tools(server))
# Logs go to stderr so the stdio transport's stdout stays protocol-clean. # Logs go to stderr so the stdio transport's stdout stays protocol-clean.
if config.transport == "http": if config.transport == "http":
logger.info("Serving MCP over streamable HTTP at http://%s:%d/mcp", config.host, config.port) logger.info("Serving MCP over streamable HTTP at http://%s:%d/mcp", config.host, config.port)