Skip to content
Merged
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ dependencies = [
"ast-grep-py>=0.40.3",
# * ================ Server and MCP Server Framework ==================*
# fastmcp is the core MCP server framework
"fastmcp>=2.14.0",
"fastmcp>=3.0.0,<4.0.0",
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR metadata/uv.lock pins FastMCP to 3.1.1, but this constraint allows 3.0.0. If the intent is to target 3.1.1 specifically (as described), consider bumping the minimum to 3.1.1 or updating the PR description to match the supported minimum.

Copilot uses AI. Check for mistakes.
# just used for types but we need them at runtime for Pydantic models
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
"mcp>=1.23.3",
# Runs the core admin/management server
Expand Down
25 changes: 21 additions & 4 deletions src/codeweaver/server/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@

from fastmcp import FastMCP
from fastmcp.client.transports import StreamableHttpTransport
from fastmcp.server.proxy import FastMCPProxy, ProxyClient
from fastmcp.server import create_proxy
from fastmcp.server.providers.proxy import FastMCPProxy, ProxyClient
from fastmcp.tools import Tool
from lateimport import lateimport

Expand Down Expand Up @@ -300,6 +301,10 @@ def _setup_server[TransportT: Literal["stdio", "streamable-http"]](
run_args = mutable_args.pop("run_args", {})
# Remove transport from args - it's not a FastMCP constructor parameter
mutable_args.pop("transport", None)
# FastMCP v3: include_tags/exclude_tags are no longer constructor parameters.
# Extract them here and apply via app.enable()/app.disable() after construction.
include_tags: set[str] | None = mutable_args.pop("include_tags", None)
exclude_tags: set[str] | None = mutable_args.pop("exclude_tags", None)

# Always use default middleware classes for this transport
from codeweaver.server.mcp.middleware import default_middleware_for_transport
Expand All @@ -314,6 +319,11 @@ def _setup_server[TransportT: Literal["stdio", "streamable-http"]](
)
app = register_tools(app)
app = register_middleware(app, cast(list[type[McpMiddleware]], middleware), middleware_opts)
# FastMCP v3: Apply tag-based visibility filtering using the new enable()/disable() API
if exclude_tags:
app.disable(tags=exclude_tags)
if include_tags:
app.enable(tags=include_tags, only=True)
if is_http:
from codeweaver.server.mcp.state import CwMcpHttpState

Expand Down Expand Up @@ -349,16 +359,23 @@ async def create_stdio_server(
stdio_settings = DictView(FastMcpServerSettingsDict(**cast(dict, stdio_settings)))
else:
stdio_settings = _get_fastmcp_settings_map(http=False)
app = _setup_server(stdio_settings, transport="stdio")
# Derive the stdio server name directly from settings instead of constructing
# a full FastMCP app instance just to access `app.name`.
stdio_name = cast(str | None, stdio_settings.get("name"))
Comment on lines 360 to +364
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When settings comes from bootstrap_settings() (a BasedModel), FastMcpServerSettingsDict(**cast(dict, stdio_settings)) expands only model_fields (via BasedModel.keys()), so computed fields like name/include_tags/exclude_tags are dropped. As a result, stdio_settings.get('name') may be None (and the proxy name may fall back to the generic default). Consider converting via .as_settings()/.model_dump(...) (including computed fields) before wrapping in FastMcpServerSettingsDict/DictView.

Copilot uses AI. Check for mistakes.
if settings and (http_settings := settings.get("mcp_server")) is not UNSET:
Comment on lines 361 to 365
http_settings = DictView(FastMcpServerSettingsDict(**cast(dict, http_settings)))
else:
http_settings = _get_fastmcp_settings_map(http=True)
run_args = http_settings.get("run_args", {})
run_args = http_settings.get("run_args", {})
resolved_host = host or run_args.get("host", LOCALHOST)
resolved_port = port or run_args.get("port", DEFAULT_MCP_PORT)
url = f"http://{resolved_host}:{resolved_port}{http_settings.get('path', MCP_ENDPOINT)}"
return app.as_proxy(backend=ProxyClient(transport=StreamableHttpTransport(url=url)))
# FastMCP v3: Replace app.as_proxy() with create_proxy()
# create_proxy() takes a target (URL, transport, client, etc.) and settings
return create_proxy(
target=ProxyClient(transport=StreamableHttpTransport(url=url)),
name=stdio_name or cast(str, http_settings.get("name", "codeweaver-mcp")),
)
Comment thread
bashandbone marked this conversation as resolved.
Comment on lines +362 to +378
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the stdio proxy server name source from app.name (previously derived from the constructed FastMCP instance) to the settings-derived name (or an HTTP fallback). If MCP clients rely on a stable server name for caching/discovery, this can be a visible behavior change; consider preserving the prior name value or ensuring the chosen name stays consistent across config/default paths.

Copilot uses AI. Check for mistakes.


async def create_http_server(
Expand Down
Loading
Loading