Add otel-enable flag for mcp (#583)

This commit is contained in:
Eric Gustin 2025-09-25 19:28:54 -07:00 committed by GitHub
parent 413ff2b915
commit 710cd2b48a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 46 additions and 5 deletions

View file

@ -227,6 +227,9 @@ def mcp(
False, "--reload", help="Enable auto-reload on code changes (HTTP mode only)" False, "--reload", help="Enable auto-reload on code changes (HTTP mode only)"
), ),
debug: bool = typer.Option(False, "--debug", help="Enable debug mode with verbose logging"), debug: bool = typer.Option(False, "--debug", help="Enable debug mode with verbose logging"),
otel_enable: bool = typer.Option(
False, "--otel-enable", help="Send logs to OpenTelemetry", show_default=True
),
env_file: Optional[str] = typer.Option(None, "--env-file", help="Path to environment file"), env_file: Optional[str] = typer.Option(None, "--env-file", help="Path to environment file"),
name: Optional[str] = typer.Option(None, "--name", help="Server name"), name: Optional[str] = typer.Option(None, "--name", help="Server name"),
version: Optional[str] = typer.Option(None, "--version", help="Server version"), version: Optional[str] = typer.Option(None, "--version", help="Server version"),
@ -250,7 +253,10 @@ def mcp(
# Add optional arguments # Add optional arguments
cmd.extend(["--host", host]) cmd.extend(["--host", host])
cmd.extend(["--port", str(port)]) cmd.extend(["--port", str(port)])
cmd.append("--debug") if debug:
cmd.append("--debug")
if otel_enable:
cmd.append("--otel-enable")
if tool_package: if tool_package:
cmd.extend(["--tool-package", tool_package]) cmd.extend(["--tool-package", tool_package])
if discover_installed: if discover_installed:

View file

@ -264,6 +264,11 @@ Auto-discovery looks for Python files with @tool decorated functions in:
action="store_true", action="store_true",
help="Enable debug mode with verbose logging", help="Enable debug mode with verbose logging",
) )
parser.add_argument(
"--otel-enable",
action="store_true",
help="Send logs to OpenTelemetry",
)
parser.add_argument( parser.add_argument(
"--env-file", "--env-file",
help="Path to environment file", help="Path to environment file",
@ -336,6 +341,7 @@ Auto-discovery looks for Python files with @tool decorated functions in:
port=args.port, port=args.port,
reload=args.reload, reload=args.reload,
debug=args.debug, debug=args.debug,
otel_enable=args.otel_enable,
tool_package=args.tool_package, tool_package=args.tool_package,
discover_installed=args.discover_installed, discover_installed=args.discover_installed,
show_packages=args.show_packages, show_packages=args.show_packages,

View file

@ -5,12 +5,15 @@ Creates a FastAPI application that exposes both Arcade Worker endpoints and
MCP Server endpoints over HTTP/SSE. MCP is always enabled in this integrated mode. MCP Server endpoints over HTTP/SSE. MCP is always enabled in this integrated mode.
""" """
import asyncio
import logging
from collections.abc import AsyncGenerator, AsyncIterator from collections.abc import AsyncGenerator, AsyncIterator
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from typing import Any from typing import Any
import uvicorn import uvicorn
from arcade_core.catalog import ToolCatalog from arcade_core.catalog import ToolCatalog
from arcade_serve.fastapi.telemetry import OTELHandler
from arcade_serve.fastapi.worker import FastAPIWorker from arcade_serve.fastapi.worker import FastAPIWorker
from fastapi import FastAPI from fastapi import FastAPI
from loguru import logger from loguru import logger
@ -74,6 +77,7 @@ def create_arcade_mcp(
catalog: ToolCatalog, catalog: ToolCatalog,
mcp_settings: MCPSettings | None = None, mcp_settings: MCPSettings | None = None,
debug: bool = False, debug: bool = False,
otel_enable: bool = False,
**kwargs: Any, **kwargs: Any,
) -> FastAPI: ) -> FastAPI:
""" """
@ -87,12 +91,28 @@ def create_arcade_mcp(
if secret is None: if secret is None:
secret = "dev" # noqa: S105 secret = "dev" # noqa: S105
otel_handler = OTELHandler(
enable=otel_enable,
log_level=logging.DEBUG if debug else logging.INFO,
)
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]: async def lifespan(app: FastAPI) -> AsyncIterator[None]:
async with create_lifespan(catalog, mcp_settings, **kwargs) as components: try:
app.state.mcp_server = components["mcp_server"] logger.debug(f"Server lifespan startup. OTEL enabled: {otel_enable}")
app.state.session_manager = components["session_manager"] async with create_lifespan(catalog, mcp_settings, **kwargs) as components:
yield app.state.mcp_server = components["mcp_server"]
app.state.session_manager = components["session_manager"]
yield
except (asyncio.CancelledError, KeyboardInterrupt):
logger.debug("Server lifespan cancelled.")
raise
finally:
logger.debug(f"Server lifespan shutdown. OTEL enabled: {otel_enable}")
if otel_enable and otel_handler:
otel_handler.shutdown()
await logger.complete()
logger.debug("Server lifespan shutdown complete.")
app = FastAPI( app = FastAPI(
title=(mcp_settings.server.title or mcp_settings.server.name), title=(mcp_settings.server.title or mcp_settings.server.name),
@ -103,12 +123,14 @@ def create_arcade_mcp(
lifespan=lifespan, lifespan=lifespan,
**kwargs, **kwargs,
) )
otel_handler.instrument_app(app)
# Worker endpoints # Worker endpoints
worker = FastAPIWorker( worker = FastAPIWorker(
app=app, app=app,
secret=secret, secret=secret,
disable_auth=mcp_settings.arcade.auth_disabled, disable_auth=mcp_settings.arcade.auth_disabled,
otel_meter=otel_handler.get_meter(),
) )
worker.catalog = catalog worker.catalog = catalog
@ -191,6 +213,7 @@ def create_arcade_mcp_factory() -> FastAPI:
# Read configuration from env vars that were set before running the server # Read configuration from env vars that were set before running the server
debug = os.environ.get("ARCADE_MCP_DEBUG", "false").lower() == "true" debug = os.environ.get("ARCADE_MCP_DEBUG", "false").lower() == "true"
otel_enable = os.environ.get("ARCADE_MCP_OTEL_ENABLE", "false").lower() == "true"
tool_package = os.environ.get("ARCADE_MCP_TOOL_PACKAGE") tool_package = os.environ.get("ARCADE_MCP_TOOL_PACKAGE")
discover_installed = os.environ.get("ARCADE_MCP_DISCOVER_INSTALLED", "false").lower() == "true" discover_installed = os.environ.get("ARCADE_MCP_DISCOVER_INSTALLED", "false").lower() == "true"
show_packages = os.environ.get("ARCADE_MCP_SHOW_PACKAGES", "false").lower() == "true" show_packages = os.environ.get("ARCADE_MCP_SHOW_PACKAGES", "false").lower() == "true"
@ -216,6 +239,8 @@ def create_arcade_mcp_factory() -> FastAPI:
raise RuntimeError("No tools found") raise RuntimeError("No tools found")
logger.info(f"Total tools loaded: {total_tools}") logger.info(f"Total tools loaded: {total_tools}")
if otel_enable:
logger.info("OpenTelemetry is enabled")
# Build kwargs for server creation # Build kwargs for server creation
kwargs = {} kwargs = {}
@ -228,6 +253,7 @@ def create_arcade_mcp_factory() -> FastAPI:
catalog=catalog, catalog=catalog,
mcp_settings=None, mcp_settings=None,
debug=debug, debug=debug,
otel_enable=otel_enable,
**kwargs, **kwargs,
) )
@ -238,6 +264,7 @@ def run_arcade_mcp(
port: int = 7777, port: int = 7777,
reload: bool = False, reload: bool = False,
debug: bool = False, debug: bool = False,
otel_enable: bool = False,
tool_package: str | None = None, tool_package: str | None = None,
discover_installed: bool = False, discover_installed: bool = False,
show_packages: bool = False, show_packages: bool = False,
@ -253,6 +280,7 @@ def run_arcade_mcp(
if reload: if reload:
# Set env vars for the app factory to read later # Set env vars for the app factory to read later
os.environ["ARCADE_MCP_DEBUG"] = str(debug) os.environ["ARCADE_MCP_DEBUG"] = str(debug)
os.environ["ARCADE_MCP_OTEL_ENABLE"] = str(otel_enable)
if tool_package: if tool_package:
os.environ["ARCADE_MCP_TOOL_PACKAGE"] = tool_package os.environ["ARCADE_MCP_TOOL_PACKAGE"] = tool_package
os.environ["ARCADE_MCP_DISCOVER_INSTALLED"] = str(discover_installed) os.environ["ARCADE_MCP_DISCOVER_INSTALLED"] = str(discover_installed)
@ -278,6 +306,7 @@ def run_arcade_mcp(
app = create_arcade_mcp( app = create_arcade_mcp(
catalog=catalog, catalog=catalog,
debug=debug, debug=debug,
otel_enable=otel_enable,
**kwargs, **kwargs,
) )