release: v1.5.1 — bind guard failed open, [full] would not install, DIMSCALE did nothing

Three defects found after 1.5.0 reached PyPI, two of them by the adversarial
pass over the rewritten README.

The HTTP bind guard failed open on the launch path it was written for.
`_guarded_run_async` read the bind address from the `host` kwarg and treated an
absent one as loopback. It is not: `fastmcp run` forwards that kwarg only when
`--host` was passed, and fastmcp otherwise resolves the host from
`fastmcp.settings.host`, which is environment-backed. So

    FASTMCP_HOST=0.0.0.0 fastmcp run server.py:mcp --transport http

bound every interface -- 149 tools including file open/save and AutoCAD command
execution, no token -- while the guard inspected the literal "127.0.0.1" and
waved it through. v1.4.0's changelog claimed this guard "fires on every launch
path"; it fired, and looked at the wrong host. It now resolves the effective
host, and warns rather than staying silent if it cannot.

`[com]` and `[full]` could not be installed off Windows. Both listed
`pywin32>=306` with no environment marker, and pywin32 publishes Windows wheels
and no sdist, so `pip install autocad-mcp-pro[full]` died at resolution on
Linux and macOS -- and `[full]` is the extra the README recommended to
contributors. CI never caught it because every Linux lane installs `[pdf]` or
syncs from uv.lock, whose resolution markers hide the gap. Both entries carry
`; sys_platform == "win32"` now, with a release-consistency test to keep them.

`drawing_settings({"dimscale": ...})` reported success and changed nothing
headlessly. It writes `$DIMSCALE`, which is what AutoCAD reads when it draws a
dimension, so the call worked on COM; ezdxf renders from the dimstyle table
entry and never consults the header. One call, one reported success, two
different drawings. The header value now folds into the per-dimension override
on all five dimension tools -- including `dimension_aligned` and
`dimension_angular`, which do not share the tolerance-override path and the
obvious fix would have missed. DIMSCALE 1.0 writes nothing.

README: every engine-conditional family in the feature table now says which
engine (boundary tracing, WIPEOUT/REVCLOUD/MTEXT masks, CHSPACE, typed hatch
edge paths, headless undo), and the checkout install line stopped recommending
an extra that could not resolve on the reader's platform.

1257 passed, 1 xfailed. 1.5.0 is to be yanked: its diameter and radius callouts
put wrong numbers on drawings at default settings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Umut CAN
2026-08-06 14:28:35 +03:00
co-authored by Claude Opus 5
parent 776fe5bcf1
commit 25f8737132
11 changed files with 291 additions and 19 deletions
+39 -1
View File
@@ -6599,9 +6599,47 @@ _HTTP_TRANSPORTS = {"http", "sse", "streamable-http"}
_orig_run_async = mcp.run_async
def _effective_http_host(kwargs: dict) -> str:
"""The address that will actually be bound — not the one we hope for.
This used to read ``kwargs.get("host", "127.0.0.1")`` and treat an absent
kwarg as loopback. It is not: ``fastmcp run`` forwards ``host`` only when
``--host`` was given, and otherwise fastmcp resolves it from
``fastmcp.settings.host``, which is environment-backed. So
``FASTMCP_HOST=0.0.0.0`` bound every interface while this guard inspected
the literal string ``"127.0.0.1"`` and let it through — the check failed
*open*, without a token, on the exact launch path it was written for.
If the setting cannot be resolved we fall back to loopback rather than
refusing to start, but we say so: a guard that cannot see the host is not
enforcing anything, and silence is how the original bug survived.
"""
host = kwargs.get("host")
if host:
return str(host)
try:
import fastmcp
resolved = getattr(fastmcp.settings, "host", None)
if resolved:
return str(resolved)
except Exception as exc: # pragma: no cover - defensive
log.warning(
"Could not resolve the effective HTTP host from fastmcp settings (%s); "
"assuming loopback. Pass --host explicitly to be sure of the bind guard.",
exc,
)
return "127.0.0.1"
log.warning(
"fastmcp exposes no resolvable host setting; assuming loopback. Pass "
"--host explicitly to be sure of the bind guard."
)
return "127.0.0.1"
async def _guarded_run_async(transport=None, *args, **kwargs):
if (transport in _HTTP_TRANSPORTS) or (kwargs.get("transport") in _HTTP_TRANSPORTS):
_validate_http_bind(kwargs.get("host", "127.0.0.1"))
_validate_http_bind(_effective_http_host(kwargs))
return await _orig_run_async(transport, *args, **kwargs)