Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 0 additions & 36 deletions mcp_server/app/core/lifecycle.py

This file was deleted.

29 changes: 3 additions & 26 deletions mcp_server/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,15 @@
from fastapi.responses import JSONResponse

from app.core.config import settings
from app.core.lifecycle import shutdown_event, startup_event
from app.core.logging import setup_logging
from app.mcp.fastmcp_server import mcp
from app.webhooks.circle import router as circle_webhook_router

setup_logging()
logger = structlog.get_logger(__name__)

# Create FastMCP app first to get its lifespan
mcp_app = mcp.http_app(path="/", stateless_http=True)


@asynccontextmanager
async def lifespan(app: FastAPI):
# Start FastMCP lifespan (it's a function that returns async context manager)
async with mcp_app.lifespan(app):
# Start our custom startup
await startup_event(app)
yield
# Shutdown our custom logic
await shutdown_event(app)


app = FastAPI(
title=settings.PROJECT_NAME,
lifespan=lifespan,
openapi_url=f"{settings.API_V1_STR}/openapi.json",
)
# Create unified FastMCP app (which is a FastAPI app under the hood)
app = mcp.http_app(path="/", stateless_http=True)


# Middleware for Correlation ID and Request Logging
Expand Down Expand Up @@ -81,11 +62,7 @@ async def global_exception_handler(request: Request, exc: Exception):
allow_headers=["*"],
)

# Mount FastMCP server at /mcp endpoint (stateless mode for horizontal scaling)
# Note: path="/" because FastAPI mount strips the /mcp prefix
app.mount("/mcp", mcp_app)

# Include webhook router
# Include webhook router directly on the unified app
app.include_router(
circle_webhook_router, prefix=f"{settings.API_V1_STR}/webhooks", tags=["webhooks"]
)
Expand Down
31 changes: 31 additions & 0 deletions mcp_server/app/mcp/fastmcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,36 @@

logger = structlog.get_logger(__name__)

async def mcp_lifespan(server: FastMCP):
"""Lifecycle manager for the MCP server resources."""
logger.info(
"mcp_startup_init",
env=settings.ENVIRONMENT,
project=settings.PROJECT_NAME,
network=settings.OMNICLAW_NETWORK,
)

# Validate production environment
if settings.ENVIRONMENT == "prod":
from omniclaw.onboarding import load_managed_entity_secret

api_key = settings.CIRCLE_API_KEY.get_secret_value() if settings.CIRCLE_API_KEY else None
managed_secret = load_managed_entity_secret(api_key) if api_key else None
if not settings.ENTITY_SECRET and not managed_secret:
raise RuntimeError("Missing ENTITY_SECRET in production")

# Warm up client singleton
await OmniclawPaymentClient.get_instance()
logger.info("mcp_startup_ready")

try:
yield
finally:
# Shutdown: Clean up your resources here
await OmniclawPaymentClient.close_instance()
logger.info("mcp_shutdown_complete")


mcp = FastMCP(
name="OmniClaw MCP Server",
instructions=(
Expand All @@ -40,6 +70,7 @@
"transaction sync, and trust checks."
),
auth=get_auth_provider() if settings.MCP_REQUIRE_AUTH else None,
lifespan=mcp_lifespan,
)


Expand Down
Loading