MCP servers, over streamable HTTP

A server is a row with a URL; its tools are discovered by a button and
cached, then offered beside the built-in ones. Written by hand rather than
taken from the reference SDK, because that SDK's transport does its own
connecting -- and the one thing that must not be bypassed is check_url on
every hop. Owning the transport is the point; the framing beside it is the
small part.

Sessions are per call: initialize, initialized, the call, a best-effort
DELETE. Caching one wants an owner, a TTL, eviction, a lock and a shutdown
hook, and the server may expire it under all of that anyway -- ToolContext
is a session-free snapshot precisely so nothing in a tool holds live state.

A server's names and descriptions reach the model as instructions and are
bounded before they do; what it returns is escaped preformatted text, never
markdown. Tools are namespaced per server, so two servers exposing "search"
do not collide and neither shadows a built-in.

Also: a round's calls now run together under a semaphore, results indexed
so each tool turn stays paired with its call, and generation.status names
what is running -- a remote tool is latency-bound, and a silent pause is
what a hang looks like.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaroslav Beneš
2026-08-01 16:44:29 +02:00
parent d4cefb066a
commit ecadb66414
15 changed files with 2166 additions and 12 deletions
+193
View File
@@ -301,3 +301,196 @@ def test_the_secret_survives_a_round_trip_through_the_form(client: TestClient, d
)
db.refresh(tool)
assert decrypt(tool.secret_encrypted) == "s3cret"
# --- MCP servers -------------------------------------------------------------
def _mcp_form(**overrides) -> dict:
base = {
"name": "GitHub",
"slug": "github",
"url": "https://mcp.test/rpc",
"headers": "",
"secret_placement": "none",
"secret_name": "Authorization",
"timeout": "30",
"max_chars": "8000",
"position": "0",
"enabled": "true",
"public": "true",
}
base.update(overrides)
return {key: value for key, value in base.items() if value is not None}
@pytest.fixture
def fake_mcp(mock_http, monkeypatch):
"""A server that answers initialize and tools/list.
Invented hostnames resolve to a public address; a literal IP is handed back
as itself, so a test about a private address is still testing one.
"""
import ipaddress
def resolve(host, *_args, **_kwargs):
try:
ipaddress.ip_address(host)
except ValueError:
return [(2, 1, 6, "", ("93.184.216.34", 80))]
return [(2, 1, 6, "", (host, 80))]
monkeypatch.setattr("socket.getaddrinfo", resolve)
def handler(request: httpx.Request) -> httpx.Response:
if request.method != "POST":
return httpx.Response(405)
message = json.loads(request.content)
if message.get("method") == "notifications/initialized":
return httpx.Response(202)
if message.get("method") == "initialize":
return httpx.Response(
200,
json={
"jsonrpc": "2.0",
"id": message["id"],
"result": {"protocolVersion": "2025-06-18", "serverInfo": {"name": "fake"}},
},
headers={"mcp-session-id": "s-1"},
)
return httpx.Response(
200,
json={
"jsonrpc": "2.0",
"id": message["id"],
"result": {"tools": [{"name": "search", "description": "Search."}]},
},
)
mock_http(handler)
return handler
def test_the_mcp_pages_are_refused_to_a_plain_user(client: TestClient, plain_user):
assert client.get("/admin/mcp").status_code == 403
assert client.get("/admin/mcp/new").status_code == 403
assert client.post("/admin/mcp", data=_mcp_form()).status_code == 403
def test_mcp_new_is_not_parsed_as_a_server_id(client: TestClient, registered):
response = client.get("/admin/mcp/new")
assert response.status_code == 200
assert "New MCP server" in response.text
def test_adding_a_server_discovers_its_tools(client: TestClient, db, registered, fake_mcp):
from lembas.db.models import McpServer
client.post("/admin/mcp", data=_mcp_form(), follow_redirects=False)
server = db.scalar(select(McpServer))
assert server.slug == "github"
assert [entry["name"] for entry in server.tools_json] == ["search"]
assert server.tools_json[0]["offer_name"] == "github_search"
assert server.protocol_version == "2025-06-18"
def test_refreshing_a_server_swaps_its_row(client: TestClient, db, registered, fake_mcp):
from lembas.db.models import McpServer
client.post("/admin/mcp", data=_mcp_form(), follow_redirects=False)
server = db.scalar(select(McpServer))
response = client.post(f"/admin/mcp/{server.id}/test")
assert response.status_code == 200
assert "found 1 tool." in response.text
assert f'id="mcp-{server.id}"' in response.text, "the fragment must render standalone"
def test_a_server_that_cannot_be_reached_says_so(
client: TestClient, db, registered, mock_http, monkeypatch
):
from lembas.db.models import McpServer
monkeypatch.setattr(
"socket.getaddrinfo", lambda *a, **k: [(2, 1, 6, "", ("93.184.216.34", 80))]
)
mock_http(lambda _r: httpx.Response(500, text="down"))
response = client.post("/admin/mcp", data=_mcp_form(), follow_redirects=False)
assert response.status_code == 303
server = db.scalar(select(McpServer))
assert server is not None, "the row is still saved so the URL can be corrected"
assert server.last_error
def test_a_private_url_is_not_contacted_unless_the_box_is_ticked(
client: TestClient, db, registered, fake_mcp
):
"""The row still saves, so the URL can be corrected -- but the discovery
that runs straight after it is refused, and the row says why."""
from lembas.db.models import McpServer
client.post(
"/admin/mcp", data=_mcp_form(url="http://127.0.0.1:9000/rpc"), follow_redirects=False
)
server = db.scalar(select(McpServer))
assert server.allow_private is False
assert "private or local" in server.last_error
assert server.tools_json == []
client.post(
f"/admin/mcp/{server.id}",
data=_mcp_form(url="http://127.0.0.1:9000/rpc", allow_private="true"),
follow_redirects=False,
)
client.post(f"/admin/mcp/{server.id}/test")
db.refresh(server)
assert server.last_error == ""
assert [entry["name"] for entry in server.tools_json] == ["search"]
def test_a_duplicate_mcp_slug_is_refused(client: TestClient, db, registered, fake_mcp):
from lembas.db.models import McpServer
client.post("/admin/mcp", data=_mcp_form(), follow_redirects=False)
response = client.post("/admin/mcp", data=_mcp_form(name="Other"), follow_redirects=False)
assert "already a server" in response.text
assert len(list(db.scalars(select(McpServer)))) == 1
def test_a_url_that_is_not_http_is_refused(client: TestClient, db, registered):
from lembas.db.models import McpServer
response = client.post(
"/admin/mcp", data=_mcp_form(url="ftp://mcp.test/rpc"), follow_redirects=False
)
assert "http" in response.text
assert db.scalar(select(McpServer)) is None
def test_unticking_a_tool_withholds_it(client: TestClient, db, registered, fake_mcp):
from lembas.db.models import McpServer, User
from lembas.services.mcp import registry as mcp_registry
client.post("/admin/mcp", data=_mcp_form(), follow_redirects=False)
server = db.scalar(select(McpServer))
client.post(
f"/admin/mcp/{server.id}",
data={**_mcp_form(), "tool_choices": "1", "tool_names": "search"},
follow_redirects=False,
)
db.refresh(server)
assert server.tool_overrides_json == {"search": False}
owner = db.scalar(select(User))
assert mcp_registry.tool_defs(db, owner) == []
def test_deleting_a_server_removes_it(client: TestClient, db, registered, fake_mcp):
from lembas.db.models import McpServer
client.post("/admin/mcp", data=_mcp_form(), follow_redirects=False)
server = db.scalar(select(McpServer))
client.post(f"/admin/mcp/{server.id}/delete", follow_redirects=False)
assert db.scalar(select(McpServer)) is None