build: complete upgrade to fastapi 0.141, mcp sdk v2, fastmcp 4 and httpx2
This commit is contained in:
@@ -215,7 +215,7 @@ def rpc_client_from_target(
|
||||
) -> RpcWorkflowApiClient:
|
||||
"""Build the remote workflow surface for a resolved RPC target.
|
||||
|
||||
CLI tests patch this project-owned seam instead of monkeypatching `httpx`
|
||||
CLI tests patch this project-owned seam instead of monkeypatching `httpx2`
|
||||
internals. The production path still keeps HTTP construction inside the
|
||||
transport package.
|
||||
"""
|
||||
|
||||
@@ -9,7 +9,7 @@ import typer
|
||||
from wf_cli.context import CliContext
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import httpx
|
||||
import httpx2
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -24,11 +24,11 @@ def run_cli_operation(context: CliContext, operation: Coroutine[Any, Any, T]) ->
|
||||
|
||||
# HTTP exceptions only matter after a CLI operation starts. Keep the HTTP
|
||||
# client stack out of command registration and the `wf --help` path.
|
||||
import httpx
|
||||
import httpx2
|
||||
|
||||
try:
|
||||
return asyncio.run(operation)
|
||||
except (RuntimeError, httpx.HTTPError) as exc:
|
||||
except (RuntimeError, httpx2.HTTPError) as exc:
|
||||
if context.verbose:
|
||||
raise
|
||||
# Typer 0.26 vendors Click, so external ClickException classes bypass
|
||||
@@ -37,7 +37,9 @@ def run_cli_operation(context: CliContext, operation: Coroutine[Any, Any, T]) ->
|
||||
raise typer.Exit(code=1) from exc
|
||||
|
||||
|
||||
def _operation_error_message(exc: RuntimeError | httpx.HTTPError) -> str:
|
||||
def _operation_error_message(
|
||||
exc: RuntimeError | httpx2.HTTPError,
|
||||
) -> str:
|
||||
"""Return a stable non-empty message for compact CLI error output."""
|
||||
message = str(exc)
|
||||
if message:
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mcp.server.mcpserver import MCPServer
|
||||
|
||||
from wf_api.models import (
|
||||
DeleteDeploymentResult,
|
||||
@@ -20,7 +20,7 @@ from ..workflow_surface.models import RunDeploymentResult
|
||||
from .service import WfMcpService
|
||||
|
||||
|
||||
def register_artifact_tools(server: FastMCP, service: WfMcpService) -> None:
|
||||
def register_artifact_tools(server: MCPServer, service: WfMcpService) -> None:
|
||||
"""Register stable MCP tools for saved workflow artifact inspection."""
|
||||
handlers = WorkflowSurfaceHandlers(service)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mcp.server.mcpserver import MCPServer
|
||||
|
||||
from .service import WfMcpService
|
||||
|
||||
@@ -18,7 +18,7 @@ smallest reusable piece before saving a larger workflow artifact.
|
||||
"""
|
||||
|
||||
|
||||
def register_broker_prompts(server: FastMCP, service: WfMcpService) -> None:
|
||||
def register_broker_prompts(server: MCPServer, service: WfMcpService) -> None:
|
||||
"""Register broker prompt handlers on a FastMCP server."""
|
||||
|
||||
@server.prompt(
|
||||
|
||||
@@ -3,12 +3,12 @@ from __future__ import annotations
|
||||
import json
|
||||
from dataclasses import asdict
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mcp.server.mcpserver import MCPServer
|
||||
|
||||
from .service import WfMcpService
|
||||
|
||||
|
||||
def register_broker_resources(server: FastMCP, service: WfMcpService) -> None:
|
||||
def register_broker_resources(server: MCPServer, service: WfMcpService) -> None:
|
||||
"""Register broker resource handlers on a FastMCP server."""
|
||||
|
||||
@server.resource("wf-mcp://catalog", name="catalog.all")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mcp.server.mcpserver import MCPServer
|
||||
|
||||
from wf_api import (
|
||||
WorkflowAdminApi,
|
||||
@@ -28,8 +28,8 @@ from .service.workflow_operation_context import context_from_service
|
||||
from .tools import register_broker_tools
|
||||
|
||||
|
||||
def create_broker_server(service: WfMcpService) -> FastMCP:
|
||||
server = FastMCP(
|
||||
def create_broker_server(service: WfMcpService) -> MCPServer:
|
||||
server = MCPServer(
|
||||
"wf-mcp-broker",
|
||||
instructions=(
|
||||
"A broker MCP server over one or more upstream MCP connections. "
|
||||
|
||||
@@ -7,9 +7,9 @@ from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
import anyio
|
||||
import httpx
|
||||
import httpx2
|
||||
from mcp.client.streamable_http import StreamableHTTPError
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
|
||||
from wf_artifacts import (
|
||||
DependencyDiagnostic,
|
||||
@@ -399,8 +399,8 @@ _LIVE_SOURCE_CHECK_FAILURES = (
|
||||
anyio.ClosedResourceError,
|
||||
anyio.EndOfStream,
|
||||
anyio.BrokenResourceError,
|
||||
httpx.HTTPError,
|
||||
McpError,
|
||||
httpx2.HTTPError,
|
||||
MCPError,
|
||||
StreamableHTTPError,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mcp.server.mcpserver import MCPServer
|
||||
|
||||
from ..admin_surface import register_service_admin_tools
|
||||
from .service import WfMcpService
|
||||
|
||||
|
||||
def register_broker_tools(server: FastMCP, service: WfMcpService) -> None:
|
||||
def register_broker_tools(server: MCPServer, service: WfMcpService) -> None:
|
||||
"""Register legacy bare-name broker tools from the shared admin registrar."""
|
||||
register_service_admin_tools(
|
||||
server,
|
||||
|
||||
@@ -15,7 +15,7 @@ def map_event_to_notifications(event: McpEvent) -> list[mcp_types.ServerNotifica
|
||||
notification = _list_changed_notification(event)
|
||||
if notification is None:
|
||||
return []
|
||||
return [mcp_types.ServerNotification(notification)]
|
||||
return [notification]
|
||||
|
||||
|
||||
def _list_changed_notification(
|
||||
|
||||
@@ -34,7 +34,7 @@ class FastMcpNotificationContext(Protocol):
|
||||
|
||||
async def send_notification(
|
||||
self,
|
||||
notification: mcp_types.ServerNotificationType,
|
||||
notification: mcp_types.ServerNotification,
|
||||
) -> None: ...
|
||||
|
||||
|
||||
@@ -46,4 +46,4 @@ class FastMcpContextNotificationSink:
|
||||
|
||||
async def send_event(self, event: McpEvent) -> None:
|
||||
for notification in map_event_to_notifications(event):
|
||||
await self._context.send_notification(notification.root)
|
||||
await self._context.send_notification(notification)
|
||||
|
||||
@@ -9,12 +9,12 @@ from pathlib import Path
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
import anyio
|
||||
import httpx
|
||||
import httpx2
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client.transports.config import MCPConfigTransport
|
||||
from fastmcp.server.providers.proxy import FastMCPProxy, StatefulProxyClient
|
||||
from mcp.client.streamable_http import StreamableHTTPError
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.exceptions import MCPError
|
||||
|
||||
from ..models import BrokerConfig, ConnectionConfig
|
||||
from ..proxy_config import broker_config_to_fastmcp_config
|
||||
@@ -33,8 +33,8 @@ _PROXY_LIST_FAILURES = (
|
||||
anyio.ClosedResourceError,
|
||||
anyio.EndOfStream,
|
||||
anyio.BrokenResourceError,
|
||||
httpx.HTTPError,
|
||||
McpError,
|
||||
httpx2.HTTPError,
|
||||
MCPError,
|
||||
StreamableHTTPError,
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -10,7 +10,7 @@ from .models import OpenApiOperation
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class HttpRequestParts:
|
||||
"""OpenAPI-shaped request parts ready for `httpx` execution."""
|
||||
"""OpenAPI-shaped request parts ready for `httpx2` execution."""
|
||||
|
||||
method: str
|
||||
url: str
|
||||
|
||||
@@ -4,8 +4,8 @@ from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, TypeVar
|
||||
|
||||
import httpx
|
||||
from mcp import McpError
|
||||
import httpx2
|
||||
from mcp import MCPError
|
||||
from mcp.types import METHOD_NOT_FOUND
|
||||
|
||||
from wf_authoring import NodeSpec
|
||||
@@ -63,9 +63,9 @@ async def _list_optional_capabilities(
|
||||
return await load()
|
||||
except Exception as exc:
|
||||
root = _root_exception(exc)
|
||||
if isinstance(root, McpError) and root.error.code == METHOD_NOT_FOUND:
|
||||
if isinstance(root, MCPError) and root.error.code == METHOD_NOT_FOUND:
|
||||
return []
|
||||
if isinstance(root, httpx.HTTPStatusError) and root.response.status_code in {
|
||||
if isinstance(root, httpx2.HTTPStatusError) and root.response.status_code in {
|
||||
400,
|
||||
404,
|
||||
}:
|
||||
|
||||
@@ -14,7 +14,7 @@ from .protocols import ToolCallResult
|
||||
|
||||
def tool_to_discovered(tool: McpTool) -> DiscoveredTool:
|
||||
"""Convert an MCP SDK tool into the source discovery model."""
|
||||
output_schema = workflow_output_schema_from_mcp_tool_schema(tool.outputSchema)
|
||||
output_schema = workflow_output_schema_from_mcp_tool_schema(tool.output_schema)
|
||||
display_name = (
|
||||
tool.annotations.title
|
||||
if tool.annotations is not None and tool.annotations.title
|
||||
@@ -24,7 +24,7 @@ def tool_to_discovered(tool: McpTool) -> DiscoveredTool:
|
||||
name=tool.name,
|
||||
title=display_name,
|
||||
description=tool.description,
|
||||
input_schema=tool.inputSchema,
|
||||
input_schema=tool.input_schema,
|
||||
output_schema=output_schema,
|
||||
outcomes=("ok", "error"),
|
||||
metadata=tool.model_dump(by_alias=True, mode="json"),
|
||||
@@ -55,7 +55,7 @@ def resource_to_discovered(resource: McpResource) -> DiscoveredResource:
|
||||
name=local_name,
|
||||
title=resource.title,
|
||||
description=resource.description,
|
||||
mime_type=resource.mimeType,
|
||||
mime_type=resource.mime_type,
|
||||
metadata=resource.model_dump(by_alias=True, mode="json"),
|
||||
)
|
||||
|
||||
@@ -77,14 +77,14 @@ def prompt_to_discovered(prompt: McpPrompt) -> DiscoveredPrompt:
|
||||
|
||||
def tool_result_to_call_result(result: McpCallToolResult) -> ToolCallResult:
|
||||
"""Convert an MCP SDK tool call result into the adapter result model."""
|
||||
if result.structuredContent is not None:
|
||||
output = result.structuredContent
|
||||
if result.structured_content is not None:
|
||||
output = result.structured_content
|
||||
else:
|
||||
output: dict[str, Any] = {
|
||||
"content": [item.model_dump(by_alias=True) for item in result.content]
|
||||
}
|
||||
return ToolCallResult(
|
||||
outcome="error" if result.isError else "ok",
|
||||
outcome="error" if result.is_error else "ok",
|
||||
output=output,
|
||||
meta=result.meta or {},
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ from dataclasses import dataclass
|
||||
from typing import Any, Protocol
|
||||
from uuid import uuid4
|
||||
|
||||
import httpx
|
||||
import httpx2
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -48,7 +48,7 @@ class RpcClientTransport:
|
||||
|
||||
url: str
|
||||
timeout_seconds: float = 30.0
|
||||
http_client: httpx.AsyncClient | None = None
|
||||
http_client: httpx2.AsyncClient | None = None
|
||||
|
||||
async def _call(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
|
||||
request_id = uuid4().hex
|
||||
@@ -59,7 +59,7 @@ class RpcClientTransport:
|
||||
"params": params,
|
||||
}
|
||||
if self.http_client is None:
|
||||
async with httpx.AsyncClient(timeout=self.timeout_seconds) as client:
|
||||
async with httpx2.AsyncClient(timeout=self.timeout_seconds) as client:
|
||||
response = await client.post(self.url, json=request)
|
||||
else:
|
||||
response = await self.http_client.post(self.url, json=request)
|
||||
|
||||
Reference in New Issue
Block a user