fastmcp/tests/server/providers/test_addressing.py
Jeremiah Lowin af957e773f
Replace ___ with hash-based backend tool routing and per-tool prefab resources (#3824)
Replaces the ___ separator for FastMCPApp backend tool routing with a
deterministic hash(app_name, tool_name) prefix, and replaces the shared
singleton prefab renderer resource with per-tool resources synthesized
on demand.

Backend tools are now callable via <hash>_<local_name> instead of
<app_name>___<local_name>. The dispatcher walks the provider tree
recursively via get_tool_by_hash (same pattern as get_app_tool).

Each prefab tool gets its own renderer resource at
ui://prefab/tool/<hash>/renderer.html with per-tool CSP — fixing the
bug where PrefabAppConfig(csp=...) never actually applied.

Closes #3735, closes #3805
2026-04-12 12:52:07 -04:00

66 lines
2.1 KiB
Python

"""Tests for the tool hashing primitives."""
from __future__ import annotations
from fastmcp.server.providers.addressing import (
HASH_LENGTH,
hash_tool,
hashed_backend_name,
hashed_resource_uri,
parse_hashed_backend_name,
parse_hashed_resource_uri,
)
class TestHashFunction:
def test_hash_is_fixed_length_hex(self):
h = hash_tool("myapp", "greet")
assert len(h) == HASH_LENGTH
assert all(c in "0123456789abcdef" for c in h)
def test_same_inputs_same_hash(self):
a = hash_tool("app", "submit_form")
b = hash_tool("app", "submit_form")
assert a == b
def test_different_app_names_different_hash(self):
a = hash_tool("contacts", "save")
b = hash_tool("billing", "save")
assert a != b
def test_different_tool_names_different_hash(self):
a = hash_tool("app", "save")
b = hash_tool("app", "delete")
assert a != b
class TestBackendNameRoundtrip:
def test_format_and_parse(self):
name = hashed_backend_name("contacts", "submit_form")
parsed = parse_hashed_backend_name(name)
assert parsed is not None
digest, local = parsed
assert digest == hash_tool("contacts", "submit_form")
assert local == "submit_form"
def test_parse_rejects_short_strings(self):
assert parse_hashed_backend_name("foo") is None
def test_parse_rejects_non_hex_prefix(self):
assert parse_hashed_backend_name("zzzzzzzzzzzz_save") is None
def test_parse_rejects_missing_separator(self):
assert parse_hashed_backend_name("abcdef012345save") is None
class TestResourceUriRoundtrip:
def test_format_and_parse(self):
uri = hashed_resource_uri("dashboard", "show")
h = parse_hashed_resource_uri(uri)
assert h == hash_tool("dashboard", "show")
def test_parse_rejects_unrelated_uri(self):
assert parse_hashed_resource_uri("file:///etc/passwd") is None
def test_parse_rejects_wrong_length_hash(self):
assert parse_hashed_resource_uri("ui://prefab/tool/abc/renderer.html") is None