mirror of
https://github.com/hansjone/netx.git
synced 2026-10-18 05:20:47 +08:00
MCP: serve tools from the API process over Streamable HTTP at /mcp
- netx_api/mcp_remote: ops / topology / bizMonitor tools, Bearer auth, per-tool RBAC filtering, ?groups= filter, readOnlyHint and group meta. - Mount the official MCP Streamable HTTP app at /mcp (stateless); AuthAuditMiddleware is pure ASGI and skips /mcp. - Let uvicorn chunk MCP responses; some Windows LSPs rewrite Content-Length replies on */mcp* paths into broken chunked framing. - NETX_MCP_ALLOWED_HOSTS opts into DNS-rebinding checks (off by default so LAN and reverse-proxy hosts work; Bearer is the gate). - Remove the three stdio Python MCP packages; tests moved to /mcp, including real-TCP framing regressions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
918ece86c3
commit
52679a7cfc
150 changed files with 2367 additions and 2812 deletions
54
tests/mcp_remote/topology/test_untangle.py
Normal file
54
tests/mcp_remote/topology/test_untangle.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
"""Untangle must not fling nodes across the canvas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
from netx_api.mcp_remote.topology.layout_ops import build_state_from_nodes_edges
|
||||
from netx_api.mcp_remote.topology.layout_ops.untangle import _MAX_JUMP, _candidates, untangle_crossings
|
||||
|
||||
|
||||
def test_candidates_stay_near_node() -> None:
|
||||
pos = {
|
||||
"a": (0.0, 0.0),
|
||||
"b": (5000.0, 0.0), # far neighbor — old code used dx*3.5 → 17k jump
|
||||
"c": (2500.0, 800.0),
|
||||
}
|
||||
adj = {"c": {"a", "b"}, "a": {"c"}, "b": {"c"}}
|
||||
import random
|
||||
|
||||
cands = _candidates(pos, "c", adj, random.Random(1))
|
||||
assert cands
|
||||
x0, y0 = pos["c"]
|
||||
for x, y in cands:
|
||||
assert math.hypot(x - x0, y - y0) <= _MAX_JUMP + 1e-6
|
||||
|
||||
|
||||
def test_untangle_preserves_bbox_order() -> None:
|
||||
# Long chain with one intentional crossing-ish layout; moves stay local.
|
||||
nodes = []
|
||||
edges = []
|
||||
for i in range(8):
|
||||
nodes.append(
|
||||
{
|
||||
"fabric_node_id": f"n{i}",
|
||||
"name": f"N{i}-EN-1",
|
||||
"x": float(i * 200),
|
||||
"y": 0.0 if i % 2 == 0 else 40.0,
|
||||
}
|
||||
)
|
||||
if i:
|
||||
edges.append({"a_node_id": f"n{i-1}", "b_node_id": f"n{i}"})
|
||||
# add a long chord that crosses nothing badly but gives degree
|
||||
edges.append({"a_node_id": "n0", "b_node_id": "n7"})
|
||||
st = build_state_from_nodes_edges(nodes, edges)
|
||||
before = dict(st.positions)
|
||||
xs0 = [p[0] for p in before.values()]
|
||||
ys0 = [p[1] for p in before.values()]
|
||||
span0 = max(max(xs0) - min(xs0), max(ys0) - min(ys0))
|
||||
out = untangle_crossings(st, max_rounds=40, max_degree=7, target_crossings=0)
|
||||
xs1 = [p[0] for p in out.state.positions.values()]
|
||||
ys1 = [p[1] for p in out.state.positions.values()]
|
||||
span1 = max(max(xs1) - min(xs1), max(ys1) - min(ys1))
|
||||
# Must not explode bbox by >2x
|
||||
assert span1 <= span0 * 2.0 + 800.0
|
||||
Loading…
Add table
Add a link
Reference in a new issue