From 42a3ee54a89c46a02cffc615c71bd1c1045508a2 Mon Sep 17 00:00:00 2001 From: segundavid-dev Date: Thu, 21 May 2026 14:14:22 +0100 Subject: [PATCH 1/4] feat(mcp): add version://current resource and --version flag --- src/reflex/cli.py | 28 ++++++++++++++++++++++++++++ src/reflex/mcp/server.py | 13 +++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/reflex/cli.py b/src/reflex/cli.py index 527ef961..6bc55d5e 100644 --- a/src/reflex/cli.py +++ b/src/reflex/cli.py @@ -4898,5 +4898,33 @@ def data_revoke() -> None: console.print(f"Revoked: {removed} files deleted. Data contribution disabled.") +# ── MCP subcommand ───────────────────────────────────────────────────────── + +mcp_app = typer.Typer(name="mcp", help="MCP server utilities.", no_args_is_help=True) +app.add_typer(mcp_app, name="mcp") + + +def _mcp_version_callback(value: bool) -> None: + if value: + typer.echo(f"reflex-mcp {__version__}") + raise typer.Exit() + + +@mcp_app.callback(invoke_without_command=True) +def mcp_main( + ctx: typer.Context, + version: bool = typer.Option( + None, + "--version", + help="Show MCP server version and exit.", + callback=_mcp_version_callback, + is_eager=True, + ), +) -> None: + """MCP server utilities. Use --version to check the running server version.""" + if ctx.invoked_subcommand is None: + typer.echo(ctx.get_help()) + + if __name__ == "__main__": app() diff --git a/src/reflex/mcp/server.py b/src/reflex/mcp/server.py index a23ee5e3..eced22e0 100644 --- a/src/reflex/mcp/server.py +++ b/src/reflex/mcp/server.py @@ -10,6 +10,7 @@ - tool: `validate_dataset(dataset_path)` → {summary, checks: [...]} - resource: `metrics://prometheus` → current Prometheus exposition text + Phase 1.5 (producer-side, agents-can-plan-without-executing): - tool: `bench_latency(export_dir, iterations, warmup)` → p50/p95/p99 stats. Synchronous; defaults sized to fit MCP timeout budgets (~30s). @@ -57,6 +58,7 @@ - validate_dataset: pre-flight check a LeRobot v3.0 training dataset Available resources: +- version://current: reflex-vla package version (for client compatibility checks) - metrics://prometheus: current Prometheus metrics in text exposition format Safety note: tool `act` returns actions but does NOT actuate them. The caller is @@ -501,6 +503,17 @@ async def export_estimate( "notes": notes, } + @mcp.resource("version://current") + async def version_resource() -> str: + """Current reflex-vla package version. + + Clients can read this resource to check compatibility before issuing + tool calls. Returns a JSON string with ``version`` and ``package`` keys. + """ + import json + from reflex import __version__ + return json.dumps({"version": __version__, "package": "reflex-vla"}) + @mcp.resource("metrics://prometheus") async def prometheus_metrics() -> str: """Current Prometheus metrics in text exposition format. From 2a3dcc9519861dac5d469229949cf6fbc347d3b2 Mon Sep 17 00:00:00 2001 From: segundavid-dev Date: Thu, 21 May 2026 14:37:34 +0100 Subject: [PATCH 2/4] feat(tests): add tests for version resource registration and retrieval --- tests/test_mcp_server.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_mcp_server.py b/tests/test_mcp_server.py index aa2704a5..16bc44ed 100644 --- a/tests/test_mcp_server.py +++ b/tests/test_mcp_server.py @@ -246,6 +246,38 @@ async def test_validate_dataset_tool_returns_error_on_missing_path(): assert ("error" in payload) or ("decision" in payload) or ("summary" in payload) +# --------------------------------------------------------------------------- +# Resource: version://current +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_version_resource_registered(): + mcp = create_mcp_server(_mock_reflex_server()) + resources = await mcp.list_resources() + uris = {str(r.uri) for r in resources} + assert any("version" in uri for uri in uris) + + +@pytest.mark.asyncio +async def test_version_resource_returns_package_version(): + import json + from reflex import __version__ + + mcp = create_mcp_server(_mock_reflex_server()) + result = await mcp.read_resource("version://current") + if isinstance(result, list): + raw = "".join( + (item.text if hasattr(item, "text") else str(item)) for item in result + ) + else: + raw = result.text if hasattr(result, "text") else str(result) + + data = json.loads(raw) + assert data["version"] == __version__ + assert data["package"] == "reflex-vla" + + # --------------------------------------------------------------------------- # Resource: metrics://prometheus # --------------------------------------------------------------------------- From 19e6944be4bd4b459ce4e3c27fab02973d62cc3b Mon Sep 17 00:00:00 2001 From: Divyansh Rawat Date: Tue, 2 Jun 2026 22:02:51 +0530 Subject: [PATCH 3/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/reflex/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reflex/cli.py b/src/reflex/cli.py index 6bc55d5e..a8029edc 100644 --- a/src/reflex/cli.py +++ b/src/reflex/cli.py @@ -4906,7 +4906,7 @@ def data_revoke() -> None: def _mcp_version_callback(value: bool) -> None: if value: - typer.echo(f"reflex-mcp {__version__}") + typer.echo(f"reflex mcp {__version__}") raise typer.Exit() From 515b27d8918bf0142273e20ebfc32108a08eb59c Mon Sep 17 00:00:00 2001 From: RomirJ Date: Wed, 17 Jun 2026 15:53:13 -0700 Subject: [PATCH 4/4] Update MCP version resource for Tether --- src/tether/cli.py | 29 ------------------------ src/tether/mcp/server.py | 16 +++++++++----- tests/test_mcp_server.py | 48 ++++++++++++++++++++++------------------ 3 files changed, 37 insertions(+), 56 deletions(-) diff --git a/src/tether/cli.py b/src/tether/cli.py index dfdda523..7f4c5c4c 100644 --- a/src/tether/cli.py +++ b/src/tether/cli.py @@ -7699,34 +7699,5 @@ def data_revoke() -> None: set_contribute_data(False) console.print(f"Revoked: {removed} files deleted. Data contribution disabled.") - -# ── MCP subcommand ───────────────────────────────────────────────────────── - -mcp_app = typer.Typer(name="mcp", help="MCP server utilities.", no_args_is_help=True) -app.add_typer(mcp_app, name="mcp") - - -def _mcp_version_callback(value: bool) -> None: - if value: - typer.echo(f"reflex mcp {__version__}") - raise typer.Exit() - - -@mcp_app.callback(invoke_without_command=True) -def mcp_main( - ctx: typer.Context, - version: bool = typer.Option( - None, - "--version", - help="Show MCP server version and exit.", - callback=_mcp_version_callback, - is_eager=True, - ), -) -> None: - """MCP server utilities. Use --version to check the running server version.""" - if ctx.invoked_subcommand is None: - typer.echo(ctx.get_help()) - - if __name__ == "__main__": app() diff --git a/src/tether/mcp/server.py b/src/tether/mcp/server.py index 5d335c0f..d8e67a67 100644 --- a/src/tether/mcp/server.py +++ b/src/tether/mcp/server.py @@ -1,6 +1,6 @@ """FastMCP server factory bound to a live TetherServer. -Exposes 6 tools + 1 resource to MCP-compatible agents (Phase 1 + Phase 1.5): +Exposes 6 tools + 2 resources to MCP-compatible agents (Phase 1 + Phase 1.5): Phase 1 (consumer-side): - tool: `act(instruction, image_b64, state, episode_id?)` → action chunk + @@ -8,9 +8,9 @@ - tool: `health()` → {state, model_version, uptime_seconds, cuda_graphs_active} - tool: `models_list()` → [{id, hf_id, size_gb_fp16, hardware_fit}, ...] - tool: `validate_dataset(dataset_path)` → {summary, checks: [...]} +- resource: `version://current` → current package/runtime version - resource: `metrics://prometheus` → current Prometheus exposition text - Phase 1.5 (producer-side, agents-can-plan-without-executing): - tool: `bench_latency(export_dir, iterations, warmup)` → p50/p95/p99 stats. Synchronous; defaults sized to fit MCP timeout budgets (~30s). @@ -58,7 +58,7 @@ - validate_dataset: pre-flight check a LeRobot v3.0 training dataset Available resources: -- version://current: reflex-vla package version (for client compatibility checks) +- version://current: fastcrest-tether package version (for client compatibility checks) - metrics://prometheus: current Prometheus metrics in text exposition format Safety note: tool `act` returns actions but does NOT actuate them. The caller is @@ -505,14 +505,18 @@ async def export_estimate( @mcp.resource("version://current") async def version_resource() -> str: - """Current reflex-vla package version. + """Current fastcrest-tether package version. Clients can read this resource to check compatibility before issuing tool calls. Returns a JSON string with ``version`` and ``package`` keys. """ import json - from reflex import __version__ - return json.dumps({"version": __version__, "package": "reflex-vla"}) + from tether import __version__ + return json.dumps({ + "version": __version__, + "package": "fastcrest-tether", + "service": "tether", + }) @mcp.resource("metrics://prometheus") async def prometheus_metrics() -> str: diff --git a/tests/test_mcp_server.py b/tests/test_mcp_server.py index f9d2e518..5dfd44ff 100644 --- a/tests/test_mcp_server.py +++ b/tests/test_mcp_server.py @@ -251,31 +251,45 @@ async def test_validate_dataset_tool_returns_error_on_missing_path(): # --------------------------------------------------------------------------- +def _resource_text(result) -> str: + if isinstance(result, list): + contents = result + elif hasattr(result, "contents"): + contents = result.contents + else: + return result.text if hasattr(result, "text") else str(result) + return "".join( + ( + item.text + if hasattr(item, "text") + else item.content + if hasattr(item, "content") + else str(item) + ) + for item in contents + ) + + @pytest.mark.asyncio async def test_version_resource_registered(): - mcp = create_mcp_server(_mock_reflex_server()) + mcp = create_mcp_server(_mock_tether_server()) resources = await mcp.list_resources() uris = {str(r.uri) for r in resources} - assert any("version" in uri for uri in uris) + assert "version://current" in uris @pytest.mark.asyncio async def test_version_resource_returns_package_version(): import json - from reflex import __version__ + from tether import __version__ - mcp = create_mcp_server(_mock_reflex_server()) + mcp = create_mcp_server(_mock_tether_server()) result = await mcp.read_resource("version://current") - if isinstance(result, list): - raw = "".join( - (item.text if hasattr(item, "text") else str(item)) for item in result - ) - else: - raw = result.text if hasattr(result, "text") else str(result) - data = json.loads(raw) + data = json.loads(_resource_text(result)) assert data["version"] == __version__ - assert data["package"] == "reflex-vla" + assert data["package"] == "fastcrest-tether" + assert data["service"] == "tether" # --------------------------------------------------------------------------- @@ -288,15 +302,7 @@ async def test_metrics_resource_returns_prometheus_text(): mcp = create_mcp_server(_mock_tether_server()) # Read the metrics resource result = await mcp.read_resource("metrics://prometheus") - # read_resource returns a list of ReadResourceContents or strings depending - # on FastMCP version — handle both - if isinstance(result, list): - payload = "".join( - (item.text if hasattr(item, "text") else str(item)) - for item in result - ) - else: - payload = result.text if hasattr(result, "text") else str(result) + payload = _resource_text(result) # Real Prometheus exposition starts with # HELP or # TYPE comments assert isinstance(payload, str) assert len(payload) > 0