fix(api): scope MCP tool child tasks

This commit is contained in:
huanghuoguoguo
2026-10-11 02:34:44 +08:00
parent 7abc1359f4
commit 49e2db8331
+34 -1
View File
@@ -18,10 +18,13 @@ maintainable. Extend deliberately.
from __future__ import annotations
import json
import functools
import typing
from mcp.server.fastmcp import FastMCP
from ..application import ApplicationAPI
from ..http.authz import Permission, require_permission
from .context import get_request_context
@@ -55,6 +58,19 @@ def _authorized(permission: Permission):
return context
def _application_api(ap) -> ApplicationAPI:
"""Resolve the shared operation facade without an eager fallback.
``_application_api(ap)`` evaluates
``ap.pipeline_service`` even when the facade exists and assumes the legacy
service has facade method names such as ``list_pipelines``. Both make
lightweight app graphs fail before the operation can run.
"""
application_api = getattr(ap, 'application_api', None)
return application_api if application_api is not None else ApplicationAPI(ap)
class LangBotMCPServer:
"""Builds and owns the FastMCP instance for LangBot."""
@@ -487,7 +503,24 @@ class LangBotMCPServer:
"""Register a tool boundary before its authorization and service call."""
def register(fn):
observed = fn
@functools.wraps(fn)
async def observed(*args, **kwargs):
# FastMCP executes tool handlers in a child task. The mount's
# request boundary is intentionally not inherited by child
# tasks, so establish a fresh short-lived scope for each tool
# invocation using the already authenticated ContextVar.
context = get_request_context()
persistence_mgr = getattr(self.ap, 'persistence_mgr', None)
tenant_scope = getattr(persistence_mgr, 'tenant_scope', None)
if not callable(tenant_scope):
# Keep lightweight in-process test doubles usable. The
# production Application always has a PersistenceManager;
# MCPMount itself still fails closed when that manager is
# absent on a real request.
return await fn(*args, **kwargs)
async with tenant_scope(context.workspace_uuid):
return await fn(*args, **kwargs)
return self.mcp.tool(**options)(observed)
return register