mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-16 13:20:39 -07:00
POST /speak is a REST wrapper around voicebox.speak for agents that
don't talk MCP (shell scripts, ACP, A2A). It reads X-Voicebox-Client-Id
and uses it for the same per-client profile resolution + default
personality lookup the MCP tool does (speak.py:39-64), so its callers
are first-class clients — but the ClientIdMiddleware only stamped
last_seen_at on /mcp* paths. REST speak callers showed up as "never
seen" in Settings → MCP despite actively acting on their bindings.
Widen the stamp predicate to an explicit ("/mcp", "/speak") prefix
list, and require a path boundary on match so future routes named
/mcpfoo or /speakers don't silently inherit the stamp via the prefix.
New test_client_id_middleware.py pins the scope with 17 parametrised
cases (both the allowed set and the overlap cases that must not match).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
50 lines
1.3 KiB
Python
50 lines
1.3 KiB
Python
"""Unit tests for the ClientIdMiddleware path predicate.
|
|
|
|
Locks down which endpoints advance ``last_seen_at`` on the
|
|
``MCPClientBinding`` row. Getting this wrong is silent: the Settings UI
|
|
just shows a stale "last heard from" timestamp and bindings never get
|
|
auto-created for new REST callers.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from backend.mcp_server.context import _is_stamped_path
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"path",
|
|
[
|
|
"/mcp",
|
|
"/mcp/",
|
|
"/mcp/tools/call",
|
|
"/mcp/bindings", # admin REST; benign — frontend never sets the header
|
|
"/speak",
|
|
"/speak/",
|
|
],
|
|
)
|
|
def test_mcp_semantic_paths_are_stamped(path: str) -> None:
|
|
assert _is_stamped_path(path) is True
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"path",
|
|
[
|
|
"/",
|
|
"/health",
|
|
"/generate",
|
|
"/captures",
|
|
"/profiles",
|
|
"/profiles/abc/compose",
|
|
"/events/speak",
|
|
"/tasks/active",
|
|
"/llm/generate",
|
|
# Prefix overlap should not match — /speakers is a hypothetical
|
|
# future endpoint that shouldn't leak the stamp.
|
|
"/speakers",
|
|
# Same for anything starting with /mcpfoo.
|
|
"/mcpfoo",
|
|
],
|
|
)
|
|
def test_other_paths_are_not_stamped(path: str) -> None:
|
|
assert _is_stamped_path(path) is False
|