Add Handbook.md benchmark tasks

This commit is contained in:
DerekSurge
2026-06-24 12:44:34 -07:00
commit 72f9a65917
1800 changed files with 323589 additions and 0 deletions
@@ -0,0 +1,66 @@
from pathlib import Path
from mcp_proxy.commands.mcp import _build_subprocess_env
def test_inputdir_namespaced_per_server(tmp_path, monkeypatch):
"""When INPUTDIR is set in env, _build_subprocess_env appends server_name."""
monkeypatch.setenv("INPUTDIR", str(tmp_path))
env = _build_subprocess_env(tmp_path, server_name="google_mail")
assert env["INPUTDIR"] == str(tmp_path / "google_mail")
assert Path(env["INPUTDIR"]).is_dir()
def test_inputdir_defaults_to_base_dir(tmp_path, monkeypatch):
"""When INPUTDIR is not in env, it defaults to base_dir/server_name."""
monkeypatch.delenv("INPUTDIR", raising=False)
env = _build_subprocess_env(tmp_path, server_name="google_mail")
assert env["INPUTDIR"] == str(tmp_path / "google_mail")
assert Path(env["INPUTDIR"]).is_dir()
def test_outputdir_from_env(tmp_path, monkeypatch):
"""When OUTPUTDIR is set in env, it is namespaced per server."""
monkeypatch.setenv("OUTPUTDIR", str(tmp_path))
env = _build_subprocess_env(tmp_path, server_name="slack")
assert env["OUTPUTDIR"] == str(tmp_path / "slack")
assert Path(env["OUTPUTDIR"]).is_dir()
def test_outputdir_defaults_to_base_dir(tmp_path, monkeypatch):
"""When OUTPUTDIR is not in env, it defaults to base_dir/server_name."""
monkeypatch.delenv("OUTPUTDIR", raising=False)
env = _build_subprocess_env(tmp_path, server_name="google_mail")
assert env["OUTPUTDIR"] == str(tmp_path / "google_mail")
assert Path(env["OUTPUTDIR"]).is_dir()
def test_all_three_dirs_always_set(tmp_path, monkeypatch):
"""INPUTDIR, OUTPUTDIR, and BUNDLE_OUTPUT_DIR are always present in the returned env."""
monkeypatch.delenv("INPUTDIR", raising=False)
monkeypatch.delenv("OUTPUTDIR", raising=False)
env = _build_subprocess_env(tmp_path, server_name="jira")
assert "INPUTDIR" in env
assert "OUTPUTDIR" in env
assert "BUNDLE_OUTPUT_DIR" in env
def test_bundle_output_dir_namespaced_per_service(tmp_path, monkeypatch):
"""BUNDLE_OUTPUT_DIR is per-service so each service writes its own
services/<name>/state.json, matching the nested input bundle layout."""
monkeypatch.setenv("OUTPUTDIR", str(tmp_path))
env_a = _build_subprocess_env(tmp_path, server_name="google_mail")
env_b = _build_subprocess_env(tmp_path, server_name="shopify")
assert env_a["BUNDLE_OUTPUT_DIR"] == str(tmp_path / "services" / "google_mail")
assert env_b["BUNDLE_OUTPUT_DIR"] == str(tmp_path / "services" / "shopify")
def test_bundle_output_dir_defaults_to_base_dir(tmp_path, monkeypatch):
"""Without OUTPUTDIR in env, BUNDLE_OUTPUT_DIR is rooted at
base_dir/services/<name>."""
monkeypatch.delenv("OUTPUTDIR", raising=False)
env = _build_subprocess_env(tmp_path, server_name="emails_mock")
assert env["BUNDLE_OUTPUT_DIR"] == str(tmp_path / "services" / "emails_mock")
assert Path(env["BUNDLE_OUTPUT_DIR"]).is_dir()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,107 @@
"""Regression tests for proxy-level env compartmentalization.
This is the structural defense against agent-side secret exfiltration via
mcp tools, like core's bash.
"""
from mcp_proxy.commands.mcp import _build_subprocess_env
def test_declared_credential_is_forwarded(tmp_path, monkeypatch):
"""A service that declares a credential by exact name in its mcp.json
``env`` list does receive it — opt-in bypasses the denylist."""
monkeypatch.setenv("BRAVE_API_KEY", "secret-for-web")
env = _build_subprocess_env(tmp_path, server_name="web", declared_secrets=["BRAVE_API_KEY"])
assert env["BRAVE_API_KEY"] == "secret-for-web"
def test_secret_isolation_between_services(tmp_path, monkeypatch):
"""Same proxy env, two services with different declarations — only the
declaring service sees the secret."""
monkeypatch.setenv("BRAVE_API_KEY", "secret-do-not-leak")
web_env = _build_subprocess_env(tmp_path, server_name="web", declared_secrets=["BRAVE_API_KEY"])
core_env = _build_subprocess_env(tmp_path, server_name="core", declared_secrets=())
assert "BRAVE_API_KEY" in web_env
assert "BRAVE_API_KEY" not in core_env
def test_credential_shaped_vars_are_dropped(tmp_path, monkeypatch):
pollute = {
# KEY
"BRAVE_API_KEY": "x",
"MY_PRIVATE_KEY_PATH": "/etc/key",
"WORLDBENCH_FOO_API_KEY": "x",
"GITHUB_TOKEN": "ghp_x",
"AWS_SECRET_ACCESS_KEY": "aws-x",
"stripe_secret": "x", # case-insensitive
"DB_PASSWORD": "hunter2",
"MYSQL_PASSWD": "old-school-x",
"SSH_PASSPHRASE": "x",
"GPG_PASSPHRASE": "x",
"GOOGLE_CREDENTIALS": "json-x",
"OAUTH_CLIENT_ID": "x",
"BEARER_TOKEN": "x",
"JWT_SECRET": "x",
"JWT_SIGNING_KEY": "x",
"SESSION_COOKIE": "x",
"AUTH_COOKIE": "x",
"TOTP_SECRET": "x",
"HOTP_KEY": "x",
"DATABASE_URL": "postgres://app_user:hunter2@db.internal:5432/app",
"REDIS_URL": "redis://:hunter2@cache.internal:6379/0",
"PIP_INDEX_URL": "https://user:token@pypi.example.com/simple",
"UV_INDEX_URL": "https://user:token@uv.example.com/simple",
"JDBC_DATABASE_URL": "jdbc:postgresql://app:hunter2@db.internal/app",
"JDBC_MYSQL_URL": "jdbc:mysql://u:p@db:3306/x",
"GIT_REMOTE_URL": "https://ghp_xxxxxxxxxxxx@github.com/org/repo",
"REGISTRY_URL": "https://pypi-token-here@pypi.example.com/simple",
}
for k, v in pollute.items():
monkeypatch.setenv(k, v)
env = _build_subprocess_env(tmp_path, server_name="core", declared_secrets=())
for k in pollute:
assert k not in env, f"{k} leaked despite credential-shaped name"
def test_innocuous_vars_pass_through(tmp_path, monkeypatch):
"""Default-allow: anything without a credential keyword is forwarded.
This is what makes the simplified model work — we don't have to enumerate
every system var a subprocess might legitimately need."""
monkeypatch.setenv("PATH", "/usr/bin:/bin")
monkeypatch.setenv("HOME", "/home/model")
monkeypatch.setenv("LANG", "en_US.UTF-8")
monkeypatch.setenv("WORLDBENCH_TASK_ID", "task-123")
monkeypatch.setenv("WORLDBENCH_TOOL_SETS", "core_read")
monkeypatch.setenv("RANDOM_CUSTOM_VAR", "value")
monkeypatch.setenv("BRAVE_SEARCH_URL", "https://api.search.brave.com/res/v1/web/search")
monkeypatch.setenv("PUBLIC_API_BASE", "https://api.example.com/v1")
monkeypatch.setenv("DATABASE_URL", "postgres://localhost/dev") # no userinfo
env = _build_subprocess_env(tmp_path, server_name="core", declared_secrets=())
assert env["PATH"] == "/usr/bin:/bin"
assert env["HOME"] == "/home/model"
assert env["LANG"] == "en_US.UTF-8"
assert env["WORLDBENCH_TASK_ID"] == "task-123"
assert env["WORLDBENCH_TOOL_SETS"] == "core_read"
assert env["RANDOM_CUSTOM_VAR"] == "value"
assert env["BRAVE_SEARCH_URL"] == "https://api.search.brave.com/res/v1/web/search"
assert env["PUBLIC_API_BASE"] == "https://api.example.com/v1"
assert env["DATABASE_URL"] == "postgres://localhost/dev"
def test_url_with_userinfo_can_be_explicitly_declared(tmp_path, monkeypatch):
"""A service that legitimately needs a credentialed URL can opt in by
declaring the var name in mcp.json's ``secrets`` — declared names bypass
both the name-shape and value-shape checks."""
monkeypatch.setenv("DATABASE_URL", "postgres://app_user:hunter2@db.internal:5432/app")
db_user_env = _build_subprocess_env(tmp_path, server_name="my-db-service", declared_secrets=["DATABASE_URL"])
core_env = _build_subprocess_env(tmp_path, server_name="core", declared_secrets=())
assert "DATABASE_URL" in db_user_env
assert "DATABASE_URL" not in core_env
@@ -0,0 +1,158 @@
"""Tests for the ``--current-time`` fake-clock wiring.
The proxy runs every MCP service under the ``faketime`` wrapper when a
``--current-time`` value is supplied, so the service (and anything it spawns,
notably syntara's executeBash / executePython sandboxes) observes a clock
anchored to that instant and advancing at real wall-clock rate.
Unit tests here run anywhere — they exercise the pure mapping and the argv
assembly without launching a process. The integration test is skipped unless
the ``faketime`` wrapper is actually installed on PATH.
"""
import shutil
import subprocess
import sys
from datetime import UTC, datetime
import pytest
from mcp_proxy.commands import mcp as mcp_cmd
from mcp_proxy.commands.mcp import resolve_faketime_launch, to_faketime_spec
from mcp_proxy.service import HookStep, _service_argv
# ---------------------------------------------------------------------------
# to_faketime_spec — RFC3339/ISO-8601 -> faketime wrapper's absolute timestamp
# ---------------------------------------------------------------------------
def test_spec_utc_zulu():
assert to_faketime_spec("2025-01-15T09:00:00Z") == "2025-01-15 09:00:00"
def test_spec_naive_assumed_utc():
assert to_faketime_spec("2025-01-15T09:00:00") == "2025-01-15 09:00:00"
def test_spec_offset_converted_to_utc():
# 09:00 at +05:00 is 04:00 UTC.
assert to_faketime_spec("2025-01-15T09:00:00+05:00") == "2025-01-15 04:00:00"
def test_spec_negative_offset_crosses_midnight():
# 22:00 at -05:00 is 03:00 UTC the next day.
assert to_faketime_spec("2025-01-15T22:00:00-05:00") == "2025-01-16 03:00:00"
def test_spec_invalid_raises():
with pytest.raises(ValueError):
to_faketime_spec("not-a-timestamp")
def test_spec_empty_raises():
# An explicitly-passed empty --current-time is a bad value, not "no clock":
# run() routes it here (via `is not None`) so it fails fast rather than
# silently falling back to the real clock.
with pytest.raises(ValueError):
to_faketime_spec("")
# ---------------------------------------------------------------------------
# _service_argv — prepend the faketime wrapper when a prefix is given
# ---------------------------------------------------------------------------
# ``python``/``python3`` resolve to the proxy's own interpreter (see
# service.resolve_command), so the argv carries ``sys.executable``, not the bare
# name. The faketime prefix, when present, wraps that resolved command.
def test_argv_no_prefix():
step = HookStep(command="python", args=["-m", "server", "--http"])
assert _service_argv(step, None) == [sys.executable, "-m", "server", "--http"]
def test_argv_empty_prefix_is_noop():
step = HookStep(command="python", args=["-m", "server"])
assert _service_argv(step, []) == [sys.executable, "-m", "server"]
def test_argv_with_faketime_prefix():
step = HookStep(command="python", args=["-m", "server"])
prefix = ["faketime", "2025-01-15 09:00:00"]
assert _service_argv(step, prefix) == [
"faketime",
"2025-01-15 09:00:00",
sys.executable,
"-m",
"server",
]
# ---------------------------------------------------------------------------
# resolve_faketime_launch — command prefix + env, with fail-fast
# ---------------------------------------------------------------------------
def test_resolve_returns_prefix_and_utc_env(monkeypatch):
monkeypatch.setattr(mcp_cmd.shutil, "which", lambda _name: "/usr/bin/faketime")
prefix, env = resolve_faketime_launch("2025-01-15T09:00:00Z")
assert prefix == ["faketime", "2025-01-15 09:00:00"]
assert env == {"TZ": "UTC"}
def test_resolve_fails_fast_when_wrapper_missing(monkeypatch):
monkeypatch.setattr(mcp_cmd.shutil, "which", lambda _name: None)
with pytest.raises(SystemExit):
resolve_faketime_launch("2025-01-15T09:00:00Z")
def test_resolve_invalid_timestamp_fails(monkeypatch):
monkeypatch.setattr(mcp_cmd.shutil, "which", lambda _name: "/usr/bin/faketime")
with pytest.raises(SystemExit):
resolve_faketime_launch("not-a-timestamp")
# ---------------------------------------------------------------------------
# Integration — only when the faketime wrapper is installed
# ---------------------------------------------------------------------------
_HAS_FAKETIME = shutil.which("faketime") is not None
_SKIP_REASON = "faketime wrapper not installed on PATH"
# 2025-01-15T09:00:00Z
_FAKE_EPOCH = 1736931600
_SPEC = "2025-01-15 09:00:00"
@pytest.mark.skipif(not _HAS_FAKETIME, reason=_SKIP_REASON)
def test_faketime_fakes_date():
out = subprocess.run(
["faketime", _SPEC, "date", "+%s"],
capture_output=True,
text=True,
env={"TZ": "UTC", "PATH": __import__("os").environ["PATH"]},
timeout=30,
)
assert out.returncode == 0, out.stderr
reported = int(out.stdout.strip())
# Clock advances from the anchor, so allow a small forward window.
assert _FAKE_EPOCH <= reported < _FAKE_EPOCH + 60
@pytest.mark.skipif(not _HAS_FAKETIME, reason=_SKIP_REASON)
def test_faketime_fakes_python():
out = subprocess.run(
["faketime", _SPEC, sys.executable, "-c", "import time; print(time.time())"],
capture_output=True,
text=True,
env={"TZ": "UTC", "PATH": __import__("os").environ["PATH"]},
timeout=30,
)
assert out.returncode == 0, out.stderr
reported = float(out.stdout.strip())
assert _FAKE_EPOCH <= reported < _FAKE_EPOCH + 60
def test_fake_epoch_matches_spec():
"""Guard the integration constants against drift."""
dt = datetime(2025, 1, 15, 9, 0, 0, tzinfo=UTC)
assert int(dt.timestamp()) == _FAKE_EPOCH
@@ -0,0 +1,600 @@
"""Tests for utility functions in commands/mcp.py."""
import json
import logging
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
from types import SimpleNamespace
from typing import ClassVar
import pytest
from fastmcp import FastMCP
from mcp_proxy.commands import mcp as mcp_command
from mcp_proxy.commands.mcp import (
HIDDEN_FROM_LISTING_TAG,
HideTaggedToolsMiddleware,
_find_free_port,
_iter_packages,
_validate_tool_sets,
_wait_for_port,
build_proxy_app,
discover_mcp_servers,
)
from mcp_proxy.service import McpConfig, McpService, resolve_command
# ---------------------------------------------------------------------------
# _find_free_port
# ---------------------------------------------------------------------------
class TestFindFreePort:
def test_returns_int(self):
port = _find_free_port()
assert isinstance(port, int)
def test_port_is_in_valid_range(self):
port = _find_free_port()
assert 1024 <= port <= 65535
def test_returns_different_ports(self):
ports = {_find_free_port() for _ in range(5)}
# At least 2 distinct ports (extremely likely)
assert len(ports) >= 2
# ---------------------------------------------------------------------------
# _wait_for_port
# ---------------------------------------------------------------------------
class TestWaitForPort:
def test_returns_true_when_port_is_listening(self):
# Start a simple server
server = HTTPServer(("127.0.0.1", 0), BaseHTTPRequestHandler)
port = server.server_address[1]
thread = threading.Thread(target=server.handle_request, daemon=True)
thread.start()
try:
assert _wait_for_port(port, timeout=5.0) is True
finally:
server.server_close()
def test_returns_false_when_port_not_listening(self):
# Find a free port and don't listen on it
port = _find_free_port()
assert _wait_for_port(port, timeout=0.5) is False
# ---------------------------------------------------------------------------
# McpConfig & discover_mcp_servers
# ---------------------------------------------------------------------------
class TestMcpConfig:
def test_basic_config(self):
cfg = McpConfig.from_mcp_json(
Path("/tmp/my-service"),
{"run": {"command": "node", "args": ["index.js"]}},
)
assert cfg.name == "my-service"
assert len(cfg.run) == 1
assert cfg.run[0].command == "node"
assert cfg.run[0].args == ["index.js"]
assert cfg.setup == []
def test_run_list(self):
cfg = McpConfig.from_mcp_json(
Path("/tmp/svc"),
{"run": [{"command": "build"}, {"command": "node", "args": ["index.js"]}]},
)
assert len(cfg.run) == 2
assert cfg.run[0].command == "build"
assert cfg.run[1].command == "node"
def test_missing_run_raises(self):
with pytest.raises(ValueError, match="'run' is required"):
McpConfig.from_mcp_json(Path("/tmp/svc"), {})
def test_custom_name(self):
cfg = McpConfig.from_mcp_json(
Path("/tmp/my-service"),
{"name": "Custom", "run": {"command": "python"}},
)
assert cfg.name == "Custom"
def test_setup_single_dict(self):
cfg = McpConfig.from_mcp_json(
Path("/tmp/svc"),
{
"run": {"command": "node"},
"setup": {"command": "npm", "args": ["run", "build"]},
},
)
assert len(cfg.setup) == 1
assert cfg.setup[0].command == "npm"
def test_setup_list(self):
cfg = McpConfig.from_mcp_json(
Path("/tmp/svc"),
{
"run": {"command": "node"},
"setup": [{"command": "a"}, {"command": "b"}],
},
)
assert len(cfg.setup) == 2
def test_cwd_override(self, tmp_path):
cfg = McpConfig.from_mcp_json(
tmp_path / "svc",
{"run": {"command": "node"}, "cwd": "subdir"},
)
assert cfg.cwd == (tmp_path / "svc" / "subdir").resolve()
def test_install_single_dict(self):
cfg = McpConfig.from_mcp_json(
Path("/tmp/svc"),
{
"run": {"command": "node"},
"install": {"command": "uv", "args": ["sync", "--package", "foo"]},
},
)
assert len(cfg.install) == 1
assert cfg.install[0].command == "uv"
assert cfg.install[0].args == ["sync", "--package", "foo"]
def test_install_defaults_empty(self):
cfg = McpConfig.from_mcp_json(
Path("/tmp/svc"),
{"run": {"command": "node"}},
)
assert cfg.install == []
def test_toolsets_parsed(self):
cfg = McpConfig.from_mcp_json(
Path("/tmp/svc"),
{
"run": {"command": "node"},
"toolsets": {"read": ["t1", "t2"], "write": ["t3"]},
},
)
assert cfg.toolsets == {"read": ["t1", "t2"], "write": ["t3"]}
def test_toolsets_defaults_empty(self):
cfg = McpConfig.from_mcp_json(
Path("/tmp/svc"),
{"run": {"command": "node"}},
)
assert cfg.toolsets == {}
class TestIterPackages:
"""Iteration is the discovery primitive — it returns every package
regardless of any tool_sets filter. ``gen`` uses this directly."""
def test_iter_packages_returns_each(self, tmp_path):
pkg = tmp_path / "svc1"
pkg.mkdir()
(pkg / "mcp.json").write_text(json.dumps({"run": {"command": "node"}}))
servers = _iter_packages(tmp_path)
assert len(servers) == 1
assert servers[0].name == "svc1"
def test_exits_on_missing_run_command(self, tmp_path):
pkg = tmp_path / "bad"
pkg.mkdir()
(pkg / "mcp.json").write_text(json.dumps({"run": {}}))
with pytest.raises(SystemExit):
_iter_packages(tmp_path)
def test_exits_on_invalid_json(self, tmp_path):
pkg = tmp_path / "broken"
pkg.mkdir()
(pkg / "mcp.json").write_text("not json")
with pytest.raises(SystemExit):
_iter_packages(tmp_path)
def test_exits_on_nonexistent_root(self, tmp_path):
with pytest.raises(SystemExit):
_iter_packages(tmp_path / "nope")
def test_sorted_by_name(self, tmp_path):
for name in ("zebra", "alpha", "middle"):
pkg = tmp_path / name
pkg.mkdir()
(pkg / "mcp.json").write_text(json.dumps({"run": {"command": "python"}}))
servers = _iter_packages(tmp_path)
names = [s.name for s in servers]
assert names == ["alpha", "middle", "zebra"]
class TestDiscoverMcpServers:
def test_filter_by_namespaced_toolset(self, tmp_path):
"""Only servers declaring the requested namespaced toolset are returned."""
(tmp_path / "alpha").mkdir()
(tmp_path / "alpha" / "mcp.json").write_text(
json.dumps({"run": {"command": "node"}, "toolsets": {"read": ["t1"]}})
)
(tmp_path / "beta").mkdir()
(tmp_path / "beta" / "mcp.json").write_text(
json.dumps({"run": {"command": "node"}, "toolsets": {"write": ["t2"]}})
)
(tmp_path / "gamma").mkdir()
(tmp_path / "gamma" / "mcp.json").write_text(
json.dumps({"run": {"command": "node"}, "toolsets": {"read": ["t3"]}})
)
servers = discover_mcp_servers(tmp_path, tool_sets=["alpha_read", "gamma_read"])
assert [s.name for s in servers] == ["alpha", "gamma"]
def test_no_filter_returns_nothing(self, tmp_path):
"""tool_sets=None / [] / [""] all mean 'no servers' — callers must opt in
explicitly. Use _iter_packages when you need every package."""
for name in ("alpha", "beta"):
pkg = tmp_path / name
pkg.mkdir()
(pkg / "mcp.json").write_text(json.dumps({"run": {"command": "node"}}))
assert discover_mcp_servers(tmp_path, tool_sets=None) == []
assert discover_mcp_servers(tmp_path, tool_sets=[]) == []
assert discover_mcp_servers(tmp_path, tool_sets=[""]) == []
def test_filter_multiple_namespaced_toolsets(self, tmp_path):
"""Servers declaring any of the requested namespaced toolsets are included."""
(tmp_path / "alpha").mkdir()
(tmp_path / "alpha" / "mcp.json").write_text(
json.dumps({"run": {"command": "node"}, "toolsets": {"read": ["t1"]}})
)
(tmp_path / "beta").mkdir()
(tmp_path / "beta" / "mcp.json").write_text(
json.dumps({"run": {"command": "node"}, "toolsets": {"write": ["t2"]}})
)
(tmp_path / "gamma").mkdir()
(tmp_path / "gamma" / "mcp.json").write_text(
json.dumps({"run": {"command": "node"}, "toolsets": {"debug": ["t3"]}})
)
servers = discover_mcp_servers(tmp_path, tool_sets=["alpha_read", "beta_write"])
assert [s.name for s in servers] == ["alpha", "beta"]
def test_filter_namespaced_toolset(self, tmp_path):
"""Namespaced form 'pkg_toolset' selects only that package+toolset."""
(tmp_path / "google_mail").mkdir()
(tmp_path / "google_mail" / "mcp.json").write_text(
json.dumps({"run": {"command": "node"}, "toolsets": {"read": ["t1"], "write": ["t2"]}})
)
(tmp_path / "slack").mkdir()
(tmp_path / "slack" / "mcp.json").write_text(
json.dumps({"run": {"command": "node"}, "toolsets": {"read": ["t3"], "write": ["t4"]}})
)
(tmp_path / "jira").mkdir()
(tmp_path / "jira" / "mcp.json").write_text(
json.dumps({"run": {"command": "node"}}) # no toolsets
)
# Only google_mail should be found; slack has no google_mail_read toolset
servers = discover_mcp_servers(tmp_path, tool_sets=["google_mail_read"])
assert [s.name for s in servers] == ["google_mail"]
def test_returns_mcp_service_objects(self, tmp_path):
"""discover_mcp_servers returns McpService instances wrapping McpConfig."""
pkg = tmp_path / "svc"
pkg.mkdir()
(pkg / "mcp.json").write_text(json.dumps({"run": {"command": "node"}, "toolsets": {"read": ["t"]}}))
servers = discover_mcp_servers(tmp_path, tool_sets=["svc_read"])
assert len(servers) == 1
assert isinstance(servers[0], McpService)
assert isinstance(servers[0].config, McpConfig)
def test_bare_package_name_is_rejected(self, tmp_path):
"""Bare package names are not valid tool_sets — they must be namespaced
toolset names (``google_mail_read``), so validation rejects them."""
(tmp_path / "google_mail").mkdir()
(tmp_path / "google_mail" / "mcp.json").write_text(
json.dumps({"run": {"command": "node"}, "toolsets": {"read": ["t1"]}})
)
# "google_mail" is a package name, not a toolset name — unknown tool set.
with pytest.raises(SystemExit):
discover_mcp_servers(tmp_path, tool_sets=["google_mail"])
def _core_and_shim(self, tmp_path):
"""Create fake ``core`` and ``syntara`` (compat shim) packages."""
(tmp_path / "core").mkdir()
(tmp_path / "core" / "mcp.json").write_text(
json.dumps({"run": {"command": "python"}, "toolsets": {"read": ["readFile"]}})
)
(tmp_path / "syntara").mkdir()
(tmp_path / "syntara" / "mcp.json").write_text(
json.dumps({"run": {"command": "python"}, "toolsets": {"read": ["readFile"]}})
)
def test_core_and_syntara_shim_together_warns(self, tmp_path, caplog):
"""core + the syntara compat shim are redundant but allowed — selecting both
mounts both surfaces and warns. REMOVE with the syntara package after 2026-06-18."""
self._core_and_shim(tmp_path)
with caplog.at_level(logging.WARNING, logger="mcp_proxy"):
servers = discover_mcp_servers(tmp_path, tool_sets=["core_read", "syntara_read"])
assert sorted(s.name for s in servers) == ["core", "syntara"]
assert any("both 'core' and the legacy 'syntara'" in r.message for r in caplog.records)
def test_core_alone_is_allowed(self, tmp_path):
self._core_and_shim(tmp_path)
assert [s.name for s in discover_mcp_servers(tmp_path, tool_sets=["core_read"])] == ["core"]
def test_syntara_shim_alone_is_allowed(self, tmp_path):
self._core_and_shim(tmp_path)
assert [s.name for s in discover_mcp_servers(tmp_path, tool_sets=["syntara_read"])] == ["syntara"]
class TestValidateToolSets:
@staticmethod
def _write_pkg(root, name, toolsets):
"""Materialize a minimal ``packages/<name>/mcp.json`` declaring *toolsets*."""
pkg = root / name
pkg.mkdir()
(pkg / "mcp.json").write_text(json.dumps({"run": {"command": "node"}, "toolsets": toolsets}))
def test_valid_tool_sets_pass(self, tmp_path):
"""Namespaced tool set names that a package declares do not raise."""
self._write_pkg(tmp_path, "google_mail", {"read": []})
self._write_pkg(tmp_path, "slack", {"state": []})
_validate_tool_sets(["google_mail_read", "slack_state"], tmp_path)
def test_unknown_tool_set_exits(self, tmp_path):
"""Unknown tool set names cause sys.exit."""
self._write_pkg(tmp_path, "slack", {"read": []})
with pytest.raises(SystemExit):
_validate_tool_sets(["nonexistent"], tmp_path)
def test_partial_unknown_exits(self, tmp_path):
"""Even one unknown tool set among valid ones causes exit."""
self._write_pkg(tmp_path, "slack", {"read": []})
with pytest.raises(SystemExit):
_validate_tool_sets(["slack_read", "bad_name"], tmp_path)
def test_empty_request_passes(self, tmp_path):
"""No requested tool sets is vacuously valid (nothing unknown)."""
self._write_pkg(tmp_path, "slack", {"read": []})
_validate_tool_sets([], tmp_path) # should not raise
class TestBuildProxyApp:
def test_exits_when_selected_service_fails_startup(self, tmp_path, monkeypatch, caplog):
"""Requested services that die during startup abort the whole proxy."""
class FakeProc:
returncode = 7
def poll(self):
return self.returncode
def terminate(self):
pass
class FakeService:
name = "jira"
secrets: ClassVar[list[str]] = []
config = SimpleNamespace(startup_timeout=0.1)
def run_install(self, env):
return True
def run_setup(self, env):
return True
def run_pre_steps(self, env):
return True
def start_service(self, env, port, proxy_token, command_prefix=None):
return FakeProc()
monkeypatch.setattr(
"mcp_proxy.commands.mcp.discover_mcp_servers",
lambda packages_root, tool_sets=None: [FakeService()],
)
monkeypatch.setattr(
"mcp_proxy.commands.mcp._build_subprocess_env",
lambda base_dir, server_name, declared_secrets=(): {},
)
with pytest.raises(SystemExit):
build_proxy_app(tmp_path, tmp_path, tool_sets=["jira_read"])
assert "Aborting startup because requested MCP services failed to start" in caplog.text
assert "jira (exited immediately with code 7)" in caplog.text
class TestRunViewerStartup:
class FakeApp:
def __init__(self):
self.run_calls = []
def custom_route(self, *_args, **_kwargs):
def decorator(fn):
return fn
return decorator
def run(self, **kwargs):
self.run_calls.append(kwargs)
def _stub_proxy_run(self, monkeypatch, tmp_path):
app = self.FakeApp()
monkeypatch.setenv("WORLDBENCH_ROOT", str(tmp_path))
monkeypatch.setenv("WORLDBENCH_PACKAGES_ROOT", str(tmp_path))
monkeypatch.delenv("WORLDBENCH_TOOL_SETS", raising=False)
monkeypatch.delenv("PORT", raising=False)
monkeypatch.setattr(mcp_command, "install_crash_handlers", lambda: None)
monkeypatch.setattr(mcp_command, "build_proxy_app", lambda **_kwargs: app)
monkeypatch.setattr(mcp_command, "_assert_local_tools_async", lambda _app: None)
monkeypatch.setattr(mcp_command, "_kill_child_processes", lambda: None)
return app
def test_does_not_start_viewer_when_viewer_port_is_unset(self, tmp_path, monkeypatch):
app = self._stub_proxy_run(monkeypatch, tmp_path)
viewer_calls = []
monkeypatch.delenv("VIEWER_PORT", raising=False)
monkeypatch.setattr(mcp_command, "_start_viewer_server", lambda host, port: viewer_calls.append((host, port)))
mcp_command.run(method="stdio")
assert viewer_calls == []
assert app.run_calls == [{"show_banner": False}]
def test_starts_viewer_when_viewer_port_is_set(self, tmp_path, monkeypatch):
app = self._stub_proxy_run(monkeypatch, tmp_path)
viewer_calls = []
monkeypatch.setenv("VIEWER_PORT", "8765")
monkeypatch.setattr(mcp_command, "_start_viewer_server", lambda host, port: viewer_calls.append((host, port)))
mcp_command.run(method="stdio")
assert viewer_calls == [("0.0.0.0", 8765)]
assert app.run_calls == [{"show_banner": False}]
class TestHideTaggedToolsMiddleware:
"""The proxy's un-namespaced ``export_state`` / ``import_state`` are
infrastructure for the grading harness: they must remain callable so
snapshots round-trip, but they must NOT appear in ``tools/list`` (an
agent shouldn't even know they exist).
"""
@pytest.mark.anyio
async def test_tagged_tools_are_hidden_from_list_but_callable(self):
app = FastMCP("test")
app.add_middleware(HideTaggedToolsMiddleware())
@app.tool(name="visible")
async def visible() -> dict:
return {"v": True}
@app.tool(name="hidden", tags={HIDDEN_FROM_LISTING_TAG})
async def hidden() -> dict:
return {"h": True}
listed = {t.name for t in await app.list_tools()}
assert listed == {"visible"}, f"hidden tool leaked into list_tools: {listed}"
# Hidden tool must still be callable — that's the point.
hidden_result = await app.call_tool("hidden", {})
assert hidden_result.structured_content == {"h": True}
visible_result = await app.call_tool("visible", {})
assert visible_result.structured_content == {"v": True}
@pytest.fixture
def anyio_backend(self):
return "asyncio"
class TestMcpServiceLifecycle:
@pytest.fixture
def env(self):
"""Return a minimal env dict with PATH so subprocesses can find python."""
import os
return dict(os.environ)
def test_run_install_executes_steps(self, tmp_path, env):
"""Install steps are executed and create side effects."""
marker = tmp_path / "installed.txt"
cfg = McpConfig.from_mcp_json(
tmp_path,
{
"run": {"command": "python", "args": ["-c", "pass"]},
"install": {"command": "python", "args": ["-c", f"open('{marker}', 'w').write('ok')"]},
},
)
svc = McpService(cfg)
assert svc.run_install(env) is True
assert marker.read_text() == "ok"
def test_run_install_returns_true_when_empty(self, tmp_path, env):
"""No install steps means success."""
cfg = McpConfig.from_mcp_json(tmp_path, {"run": {"command": "python"}})
svc = McpService(cfg)
assert svc.run_install(env) is True
def test_run_install_returns_false_on_failure(self, tmp_path, env):
"""Failed install step returns False."""
cfg = McpConfig.from_mcp_json(
tmp_path,
{
"run": {"command": "python"},
"install": {"command": "python", "args": ["-c", "import sys; sys.exit(1)"]},
},
)
svc = McpService(cfg)
assert svc.run_install(env) is False
def test_run_setup_executes_steps(self, tmp_path, env):
"""Setup steps are executed and create side effects."""
marker = tmp_path / "setup.txt"
cfg = McpConfig.from_mcp_json(
tmp_path,
{
"run": {"command": "python"},
"setup": {"command": "python", "args": ["-c", f"open('{marker}', 'w').write('done')"]},
},
)
svc = McpService(cfg)
assert svc.run_setup(env) is True
assert marker.read_text() == "done"
def test_run_setup_forwards_env(self, tmp_path, env):
"""Environment variables are forwarded to setup steps."""
marker = tmp_path / "env.txt"
env["MY_VAR"] = "hello"
cfg = McpConfig.from_mcp_json(
tmp_path,
{
"run": {"command": "python"},
"setup": {
"command": "python",
"args": ["-c", f"import os; open('{marker}', 'w').write(os.environ.get('MY_VAR', 'MISSING'))"],
},
},
)
svc = McpService(cfg)
assert svc.run_setup(env) is True
assert marker.read_text() == "hello"
def test_python_steps_use_proxy_interpreter_not_path(self, tmp_path, env):
# Empty PATH: a bare `python` would be unfindable, so success proves the
# step ran the absolute sys.executable (the PATH-elimination contract).
import sys
marker = tmp_path / "interp.txt"
env["PATH"] = ""
cfg = McpConfig.from_mcp_json(
tmp_path,
{
"run": {"command": "python"},
"setup": {
"command": "python",
"args": ["-c", f"import sys; open('{marker}', 'w').write(sys.executable)"],
},
},
)
svc = McpService(cfg)
assert svc.run_setup(env) is True, "bare `python` step failed — interpreter not resolved to an absolute path"
assert marker.read_text() == sys.executable
class TestResolveCommand:
def test_bare_python_resolves_to_sys_executable(self):
import sys
assert resolve_command("python") == sys.executable
assert resolve_command("python3") == sys.executable
def test_non_python_commands_pass_through(self):
for command in ("node", "bash", "npm", "uv", "/opt/venv/bin/python", "frappe-bench/env/bin/python"):
assert resolve_command(command) == command
@@ -0,0 +1,183 @@
"""Tests for resolve_tool_transforms in mcp_proxy.service."""
from __future__ import annotations
import json
import pytest
from mcp_proxy.service import McpConfig, McpService
def _make_cfg(tmp_path, tools: list[str], toolsets: dict, name: str | None = None) -> McpService:
"""Write mcp.json + mcp-tools.generated.json and return an McpService."""
mcp_json: dict = {
"run": {"command": "python", "args": ["-m", "fake"]},
"toolsets": toolsets,
}
if name is not None:
mcp_json["name"] = name
(tmp_path / "mcp.json").write_text(json.dumps(mcp_json))
tools_data = {
"schema_version": "v1",
"tools": [{"name": t, "description": "", "inputSchema": {}} for t in tools],
"toolsets": toolsets,
}
(tmp_path / "mcp-tools.generated.json").write_text(json.dumps(tools_data))
return McpService(McpConfig.from_mcp_json(tmp_path, mcp_json))
def _enabled_tools(transforms: dict) -> dict[str, str]:
"""Return {original_name: new_name} for enabled tools only."""
return {name: t.name for name, t in transforms.items() if t.enabled and t.name}
def _disabled_tools(transforms: dict) -> set[str]:
"""Return set of original names for disabled tools."""
return {name for name, t in transforms.items() if not t.enabled}
TOOLS = ["echo", "readFile", "writeFile", "bash"]
TOOLSETS = {
"read": ["echo", "readFile"],
"write": ["writeFile", "bash"],
}
def test_empty_tool_sets_exposes_all_tools(tmp_path):
"""An empty filter (or unset env var) means 'expose every introspected tool'."""
cfg = _make_cfg(tmp_path, TOOLS, TOOLSETS)
result = cfg.resolve_tool_transforms([])
assert set(_enabled_tools(result).keys()) == set(TOOLS)
assert _disabled_tools(result) == set()
def test_namespaced_read_filters_correctly(tmp_path):
pkg = tmp_path / "slack"
pkg.mkdir()
cfg = _make_cfg(pkg, TOOLS, TOOLSETS)
result = cfg.resolve_tool_transforms(["slack_read"])
assert set(_enabled_tools(result).keys()) == {"echo", "readFile"}
assert _disabled_tools(result) == {"writeFile", "bash"}
def test_namespaced_write_filters_correctly(tmp_path):
pkg = tmp_path / "slack"
pkg.mkdir()
cfg = _make_cfg(pkg, TOOLS, TOOLSETS)
result = cfg.resolve_tool_transforms(["slack_write"])
assert set(_enabled_tools(result).keys()) == {"writeFile", "bash"}
assert _disabled_tools(result) == {"echo", "readFile"}
def test_multiple_namespaced_toolsets_are_unioned(tmp_path):
pkg = tmp_path / "slack"
pkg.mkdir()
cfg = _make_cfg(pkg, TOOLS, TOOLSETS)
result = cfg.resolve_tool_transforms(["slack_read", "slack_write"])
assert set(_enabled_tools(result).keys()) == set(TOOLS)
assert _disabled_tools(result) == set()
def test_tools_are_namespaced(tmp_path):
pkg = tmp_path / "slack"
pkg.mkdir()
cfg = _make_cfg(pkg, TOOLS, TOOLSETS)
result = cfg.resolve_tool_transforms(["slack_read"])
for original, new_name in _enabled_tools(result).items():
assert new_name == f"slack__{original}"
def test_unnamespaced_package_exposes_bare_tool_names(tmp_path):
"""When mcp.json sets namespaced=false, tools are exposed without a prefix."""
mcp_json = {
"run": {"command": "python", "args": ["-m", "fake"]},
"namespaced": False,
"toolsets": TOOLSETS,
}
(tmp_path / "mcp.json").write_text(json.dumps(mcp_json))
tools_data = {
"schema_version": "v1",
"tools": [{"name": t, "description": "", "inputSchema": {}} for t in TOOLS],
"toolsets": TOOLSETS,
}
(tmp_path / "mcp-tools.generated.json").write_text(json.dumps(tools_data))
cfg = McpService(McpConfig.from_mcp_json(tmp_path, mcp_json))
result = cfg.resolve_tool_transforms([])
for original, new_name in _enabled_tools(result).items():
assert new_name == original
def test_no_toolsets_in_mcp_json_exposes_all(tmp_path):
"""When mcp.json has no toolsets key, all tools are exposed regardless of filter."""
cfg = _make_cfg(tmp_path, TOOLS, {})
result = cfg.resolve_tool_transforms(["whatever"])
assert set(_enabled_tools(result).keys()) == set(TOOLS)
assert _disabled_tools(result) == set()
def test_unknown_toolset_disables_all(tmp_path):
"""A namespaced toolset that doesn't exist on this package disables every tool."""
pkg = tmp_path / "slack"
pkg.mkdir()
cfg = _make_cfg(pkg, TOOLS, TOOLSETS)
result = cfg.resolve_tool_transforms(["slack_nonexistent"])
assert _enabled_tools(result) == {}
assert _disabled_tools(result) == set(TOOLS)
def test_other_packages_namespace_does_not_match(tmp_path):
"""A toolset namespaced to a different package is ignored — matches no tools."""
pkg = tmp_path / "slack"
pkg.mkdir()
cfg = _make_cfg(pkg, TOOLS, TOOLSETS)
result = cfg.resolve_tool_transforms(["jira_read"])
assert _enabled_tools(result) == {}
assert _disabled_tools(result) == set(TOOLS)
def test_missing_generated_json_raises(tmp_path):
mcp_json = {"run": {"command": "python", "args": []}, "toolsets": TOOLSETS}
(tmp_path / "mcp.json").write_text(json.dumps(mcp_json))
cfg = McpService(McpConfig.from_mcp_json(tmp_path, mcp_json))
with pytest.raises(FileNotFoundError, match=r"mcp-tools\.generated\.json not found"):
cfg.resolve_tool_transforms(["slack_read"])
# ---------------------------------------------------------------------------
# State toolsets are reachable when explicitly requested. The "all" / "_all"
# special case (with its hidden-by-default semantics for state/grading tools)
# was removed when the auto-aggregated cross-package toolsets were dropped.
# ---------------------------------------------------------------------------
STATEFUL_TOOLS = ["echo", "readFile", "export_state", "import_state"]
STATEFUL_TOOLSETS = {
"read": ["echo", "readFile"],
"state": ["export_state", "import_state"],
}
def test_namespaced_state_request_exposes_state(tmp_path):
pkg = tmp_path / "slack"
pkg.mkdir()
cfg = _make_cfg(pkg, STATEFUL_TOOLS, STATEFUL_TOOLSETS)
result = cfg.resolve_tool_transforms(["slack_state"])
assert set(_enabled_tools(result).keys()) == {"export_state", "import_state"}
def test_namespaced_read_does_not_expose_state(tmp_path):
pkg = tmp_path / "slack"
pkg.mkdir()
cfg = _make_cfg(pkg, STATEFUL_TOOLS, STATEFUL_TOOLSETS)
result = cfg.resolve_tool_transforms(["slack_read"])
assert set(_enabled_tools(result).keys()) == {"echo", "readFile"}
assert _disabled_tools(result) == {"export_state", "import_state"}
def test_combined_namespaced_toolsets_union(tmp_path):
pkg = tmp_path / "slack"
pkg.mkdir()
cfg = _make_cfg(pkg, STATEFUL_TOOLS, STATEFUL_TOOLSETS)
result = cfg.resolve_tool_transforms(["slack_read", "slack_state"])
assert set(_enabled_tools(result).keys()) == set(STATEFUL_TOOLS)
@@ -0,0 +1,89 @@
"""Tests for service_http module."""
import pytest
from starlette.testclient import TestClient
from mcp_proxy.service_http import ProxyTokenMiddleware, _placeholder_html
# ---------------------------------------------------------------------------
# Placeholder HTML
# ---------------------------------------------------------------------------
class TestPlaceholderHtml:
def test_contains_service_name(self):
html = _placeholder_html("My Service")
assert "My Service" in html
def test_is_valid_html(self):
html = _placeholder_html("Test")
assert html.startswith("<!DOCTYPE html>")
assert "</html>" in html
# ---------------------------------------------------------------------------
# ProxyTokenMiddleware
# ---------------------------------------------------------------------------
class TestProxyTokenMiddleware:
@pytest.fixture
def app(self):
"""Create a minimal Starlette app with ProxyTokenMiddleware."""
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.requests import Request
from starlette.responses import PlainTextResponse
from starlette.routing import Route
async def index(request: Request):
return PlainTextResponse("ok")
async def mcp_endpoint(request: Request):
return PlainTextResponse("mcp ok")
return Starlette(
routes=[
Route("/", index),
Route("/mcp", mcp_endpoint),
Route("/mcp/test", mcp_endpoint),
Route("/api/data", index),
],
middleware=[Middleware(ProxyTokenMiddleware)],
)
def test_mcp_path_bypasses_auth(self, app, monkeypatch):
monkeypatch.setenv("MCP_PROXY_TOKEN", "secret")
client = TestClient(app, raise_server_exceptions=False)
resp = client.get("/mcp")
assert resp.status_code == 200
def test_mcp_subpath_bypasses_auth(self, app, monkeypatch):
monkeypatch.setenv("MCP_PROXY_TOKEN", "secret")
client = TestClient(app, raise_server_exceptions=False)
resp = client.get("/mcp/test")
assert resp.status_code == 200
def test_non_mcp_blocked_without_token(self, app, monkeypatch):
monkeypatch.setenv("MCP_PROXY_TOKEN", "secret")
client = TestClient(app, raise_server_exceptions=False)
resp = client.get("/")
assert resp.status_code == 403
def test_non_mcp_allowed_with_correct_token(self, app, monkeypatch):
monkeypatch.setenv("MCP_PROXY_TOKEN", "secret")
client = TestClient(app, raise_server_exceptions=False)
resp = client.get("/", headers={"x-proxy-token": "secret"})
assert resp.status_code == 200
def test_non_mcp_blocked_with_wrong_token(self, app, monkeypatch):
monkeypatch.setenv("MCP_PROXY_TOKEN", "secret")
client = TestClient(app, raise_server_exceptions=False)
resp = client.get("/", headers={"x-proxy-token": "wrong"})
assert resp.status_code == 403
def test_no_token_set_allows_all(self, app, monkeypatch):
monkeypatch.delenv("MCP_PROXY_TOKEN", raising=False)
client = TestClient(app, raise_server_exceptions=False)
resp = client.get("/")
assert resp.status_code == 200
@@ -0,0 +1,183 @@
"""Tests for the setup pass the proxy runs during ``mcp`` startup.
There is no standalone setup command: ``build_proxy_app`` discovers MCP
servers and runs each one's ``install`` then ``setup`` hook from
``mcp.json`` (forwarding WORLDBENCH_* env vars) before starting the
server. Servers are selected by WORLDBENCH_TOOL_SETS: only servers whose
mcp.json declares at least one of the requested namespaced toolsets are
included. An empty / unset value runs no setup hooks.
These tests exercise that same discover -> install -> setup pass in
process, using the exact primitives ``build_proxy_app`` calls.
"""
import json
import os
import shutil
import tempfile
import unittest
from pathlib import Path
from unittest import mock
from mcp_proxy.commands.mcp import _build_subprocess_env, discover_mcp_servers
class SetupCommandTests(unittest.TestCase):
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
self.packages_root = Path(self.temp_dir) / "packages"
self.packages_root.mkdir()
def tearDown(self):
shutil.rmtree(self.temp_dir)
def _create_server(self, name, setup_hook=None, toolsets=None):
"""Create a fake MCP server package with an optional setup hook.
Always declares a ``"default"`` toolset (so :meth:`_run_setup` can
opt every package in by default). *toolsets* is merged on top, in
case a test wants to declare additional toolsets to filter against.
"""
pkg_dir = self.packages_root / name
pkg_dir.mkdir(parents=True)
config = {"run": {"command": "python", "args": ["-c", "pass"]}}
if setup_hook is not None:
config["setup"] = setup_hook
config["toolsets"] = {"default": [], **(toolsets or {})}
(pkg_dir / "mcp.json").write_text(json.dumps(config))
return pkg_dir
def _run_setup(self, env_overrides=None):
"""Run the startup setup pass in process and return the failed servers.
Mirrors the discover -> install -> setup loop ``build_proxy_app``
runs before booting (the only place setup hooks fire now). Returns
the list of server names whose install or setup hook failed — empty
on success.
Defaults WORLDBENCH_TOOL_SETS to every fake package's ``_default``
toolset so each test gets full coverage without spelling them out.
Override via *env_overrides* when a test wants narrower filtering.
"""
default_sets = " ".join(f"{p.name}_default" for p in sorted(self.packages_root.iterdir()) if p.is_dir())
env = {
"WORLDBENCH_ROOT": self.temp_dir,
"WORLDBENCH_PACKAGES_ROOT": str(self.packages_root),
"WORLDBENCH_TOOL_SETS": default_sets,
}
if env_overrides:
env.update(env_overrides)
failed: list[str] = []
with mock.patch.dict(os.environ, env):
base_dir = Path(os.environ["WORLDBENCH_ROOT"]).resolve()
packages_root = Path(os.environ["WORLDBENCH_PACKAGES_ROOT"])
tool_sets = os.environ["WORLDBENCH_TOOL_SETS"].split()
for cfg in discover_mcp_servers(packages_root, tool_sets=tool_sets):
hook_env = _build_subprocess_env(base_dir, cfg.name, declared_secrets=cfg.secrets)
if not cfg.run_install(hook_env) or not cfg.run_setup(hook_env):
failed.append(cfg.name)
return failed
def test_runs_setup_hook(self):
"""Setup hook from mcp.json is executed."""
marker = Path(self.temp_dir) / "setup_ran.txt"
self._create_server(
"svc",
setup_hook={
"command": "python",
"args": ["-c", f"open('{marker}', 'w').write('ok')"],
},
)
failed = self._run_setup()
assert failed == [], failed
assert marker.exists()
assert marker.read_text() == "ok"
def test_skips_server_without_setup_hook(self):
"""Servers without a setup hook are skipped gracefully."""
self._create_server("no-setup")
failed = self._run_setup()
assert failed == [], failed
def test_forwards_env_vars(self):
"""WORLDBENCH_* env vars are forwarded to the setup hook."""
marker = Path(self.temp_dir) / "env_check.txt"
self._create_server(
"svc",
setup_hook={
"command": "python",
"args": [
"-c",
f"import os; open('{marker}', 'w').write(os.environ.get('WORLDBENCH_TASK_ID', 'MISSING'))",
],
},
)
failed = self._run_setup({"WORLDBENCH_TASK_ID": "test-123"})
assert failed == [], failed
assert marker.read_text() == "test-123"
def test_respects_server_filter(self):
"""Only servers declaring the requested toolset are set up."""
marker_a = Path(self.temp_dir) / "a_ran.txt"
marker_b = Path(self.temp_dir) / "b_ran.txt"
self._create_server(
"alpha",
setup_hook={
"command": "python",
"args": ["-c", f"open('{marker_a}', 'w').write('ok')"],
},
toolsets={"read": ["tool_a"]},
)
self._create_server(
"beta",
setup_hook={
"command": "python",
"args": ["-c", f"open('{marker_b}', 'w').write('ok')"],
},
toolsets={"write": ["tool_b"]},
)
failed = self._run_setup({"WORLDBENCH_TOOL_SETS": "alpha_read"})
assert failed == [], failed
assert marker_a.exists()
assert not marker_b.exists()
def test_fails_if_setup_hook_fails(self):
"""A non-zero exit from a setup hook is reported as a failed server."""
self._create_server(
"bad",
setup_hook={
"command": "python",
"args": ["-c", "import sys; sys.exit(1)"],
},
)
failed = self._run_setup()
assert failed == ["bad"], failed
def test_multi_step_setup(self):
"""Setup hooks can be a list of steps."""
marker1 = Path(self.temp_dir) / "step1.txt"
marker2 = Path(self.temp_dir) / "step2.txt"
self._create_server(
"svc",
setup_hook=[
{"command": "python", "args": ["-c", f"open('{marker1}', 'w').write('1')"]},
{"command": "python", "args": ["-c", f"open('{marker2}', 'w').write('2')"]},
],
)
failed = self._run_setup()
assert failed == [], failed
assert marker1.read_text() == "1"
assert marker2.read_text() == "2"
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,25 @@
"""Repository-level checks for package toolset declarations."""
from __future__ import annotations
import json
from pathlib import Path
STATE_TOOLS = {"export_state", "import_state"}
def test_state_tools_only_appear_in_state_toolsets():
repo_root = Path(__file__).resolve().parents[3]
offenders: list[str] = []
for mcp_json in sorted((repo_root / "packages").glob("*/mcp.json")):
package = mcp_json.parent.name
data = json.loads(mcp_json.read_text())
for toolset, tools in data.get("toolsets", {}).items():
if toolset == "state":
continue
leaked = sorted(STATE_TOOLS.intersection(tools))
if leaked:
offenders.append(f"{package}:{toolset} -> {', '.join(leaked)}")
assert offenders == []
@@ -0,0 +1,124 @@
"""Tests for the viewer reverse-proxy app."""
import httpx
import pytest
from starlette.testclient import TestClient
from mcp_proxy.viewer import _DISPLAY_NAMES, _render_shell, create_viewer_app
# ---------------------------------------------------------------------------
# Shell rendering
# ---------------------------------------------------------------------------
class TestRenderShell:
def test_empty_registry(self):
html = _render_shell({})
assert "No services available" in html
def test_single_service(self):
html = _render_shell({"jira": 9000})
assert "Services" in html
assert "1 services" in html
assert 'data-service="jira"' in html
assert 'src="/__viewer__/set?app=jira"' in html
def test_multiple_services(self):
html = _render_shell({"jira": 9000, "slack": 9001, "google_mail": 9002})
assert "3 services" in html
for name in ("jira", "slack", "google_mail"):
assert f'data-service="{name}"' in html
def test_display_names_used(self):
html = _render_shell({"jira": 9000})
assert _DISPLAY_NAMES["jira"] in html # "Issues"
def test_unknown_service_gets_title_case(self):
html = _render_shell({"my_custom_service": 9000})
assert "My Custom Service" in html
def test_services_sorted(self):
html = _render_shell({"slack": 9001, "jira": 9000})
jira_pos = html.index('data-service="jira"')
slack_pos = html.index('data-service="slack"')
assert jira_pos < slack_pos
def test_first_service_is_default_iframe(self):
html = _render_shell({"slack": 9001, "jira": 9000})
# sorted → jira comes first
assert 'src="/__viewer__/set?app=jira"' in html
# ---------------------------------------------------------------------------
# Viewer app routes
# ---------------------------------------------------------------------------
class TestViewerApp:
@pytest.fixture
def app(self):
return create_viewer_app(
service_registry={"test_svc": 9999, "other": 8888},
proxy_token="secret123",
)
@pytest.fixture
def client(self, app):
return TestClient(app, raise_server_exceptions=False)
def test_index_returns_html(self, client):
resp = client.get("/")
assert resp.status_code == 200
assert "Services" in resp.text
def test_reverse_proxy_unknown_service_returns_404(self, client):
resp = client.get("/__viewer__/set?app=nonexistent")
assert resp.status_code == 404
assert "Unknown service" in resp.text
def test_iframe_fallback_uses_server_state_when_cookie_blocked(self, app, monkeypatch):
"""When embedded in an iframe, browsers may block the cookie. The
viewer should fall back to server-side state when any referer is
present (indicating a redirect, not a direct navigation)."""
async def mock_request(self, *args, **kwargs):
return httpx.Response(200, text="service content")
monkeypatch.setattr(httpx.AsyncClient, "request", mock_request)
client = TestClient(app, raise_server_exceptions=False)
# First, select a service via /__viewer__/set to populate server state.
resp = client.get("/__viewer__/set?app=test_svc", follow_redirects=False)
assert resp.status_code == 307
# Now simulate an iframe request with NO cookie but a referer present
# (browser stripped the path but kept the origin).
resp = client.get(
"/",
headers={"referer": "http://some-outer-host/"},
cookies={}, # no cookie — blocked by browser
)
# Should proxy to the service, not re-render the shell
assert resp.status_code == 200
assert "service content" in resp.text
assert "Services" not in resp.text # not the shell HTML
def test_iframe_fallback_not_triggered_without_referer(self, client):
"""Without a referer, the server state fallback should NOT activate —
direct navigation should always show the shell."""
resp = client.get("/", cookies={})
assert resp.status_code == 200
assert "Services" in resp.text
def test_reverse_proxy_connection_error_returns_502(self, app, monkeypatch):
# Mock httpx to simulate a connection error regardless of port state
async def mock_request(self, *args, **kwargs):
raise httpx.ConnectError("Connection refused")
monkeypatch.setattr(httpx.AsyncClient, "request", mock_request)
client = TestClient(app, raise_server_exceptions=False)
# Set the service cookie first, then make a proxied request
client.cookies.set("__viewer_service", "test_svc")
resp = client.get("/some-page", headers={"referer": "http://testserver/"})
assert resp.status_code == 502
assert "Service unavailable" in resp.text