Skip to content

v4.2.1

Latest

Choose a tag to compare

@github-actions github-actions released this 21 Oct 18:40
· 20 commits to main since this release
accbfdc

Fixed

  • LMv1 authentication: the signature is now base64 of the hex HMAC-SHA256 digest, as
    LogicMonitor documents. Base64 of the raw 32-byte digest is rejected with 401, so every
    portal configured with LM_ACCESS_ID/LM_ACCESS_KEY failed to authenticate; bearer-token
    portals were unaffected. The tests now check a fixed reference vector computed with LM's
    recipe.
  • LMv1 request bodies: request() and ingest_post() serialize a JSON body once
    and send those exact bytes, so the signature covers what goes on the wire. The
    signature was computed over json.dumps with default separators while httpx sent
    compact JSON, so every POST, PUT and PATCH with a body failed with 401 on LMv1
    portals.
  • Stdio no longer fails at startup with UnboundLocalError when LM_MULTI_PORTAL
    and TF_WORKSPACE_DIR are both set: logging is imported at module scope.
  • /readyz no longer calls the LogicMonitor API. With
    LM_HEALTH_CHECK_CONNECTIVITY on it ran the connectivity check on every probe,
    although the result could never change its answer, so an unauthenticated caller
    could drive portal traffic (#56).
  • LM_MAX_RETRIES is honored on both transports. Stdio and HTTP built the
    single-portal client with the default of 3 whatever the setting.
  • get_api_tokens and get_api_token were uncallable: their handlers required an
    admin_id that neither schema declared. admin_id is now optional and declared
    in both; omitted, tokens list portal-wide and a token resolves by id
    (filter=id:<id>), and an unknown id returns NOT_FOUND. get_website_data's
    schema marks checkpoint_id required, as the handler always did. A test holds
    every tool's schema to its handler signature.
  • Tool errors are reported as errors. Error results carry a type (ErrorContent),
    call_tool returns them as CallToolResult(isError=True), and the write audit
    log and session history take success from the result. A 403 on a write, or a
    refusal with writes disabled, was audited as success=True. The text of every
    error is unchanged.
  • The mcp floor is 1.19.0, the first release whose low-level server passes a
    CallToolResult through; with 1.18 and earlier a tool error reached the client
    as a pydantic validation message instead of its text.
  • Configuration validation errors no longer echo the rejected value, which
    previously put a too-short LM_HTTP_AUTH_TOKEN or LM_BEARER_TOKEN into the
    startup traceback, container logs, and the health endpoint's response body.
  • lm_mcp.__version__ synced to the release version (the published 4.2.0 wheel
    self-reports "4.1.0" at the HTTP root endpoint); the foundation test now
    asserts __version__ against the installed package metadata so a bump that
    misses __init__.py fails the suite instead of shipping.
  • HTTP transport tools/list now includes the Terraform tools when a Terraform
    runner is configured. They were dispatchable over HTTP but invisible to HTTP
    clients, unlike stdio which listed them correctly.
  • Documentation: removed the LM_ANALYSIS_TTL_MINUTES row from the README
    (not a config field), corrected the LM_CORS_ORIGINS default, and dropped a
    .gitignore negation for a file that does not exist.
  • server.json's version fields are held to pyproject.toml by a test, and the
    README and documentation/client-setup.md are held to the registry's tool count
    (306); the client setup guide said 280.

Added

  • LM_HTTP_AUTH_TOKEN: opt-in inbound bearer authentication for the HTTP
    transport. When set, /mcp and /api/v1/* require
    Authorization: Bearer <token> (constant-time comparison) and return 401
    with WWW-Authenticate: Bearer otherwise; /, /health, /healthz, and
    /readyz stay open for container healthchecks and orchestrator probes.
    Unset preserves previous behavior and logs a startup warning. Minimum token
    length 16; a blank value means unset, and surrounding whitespace is stripped
    so a token read from a secret file still matches. The scheme is matched
    case-insensitively per RFC 7235, and probe paths match with or without a
    trailing slash. Stdio is unaffected.
  • CONTRIBUTING.md: fork-and-pull-request workflow, development setup, PR
    expectations, and the tool-surface regeneration commands.
  • deploy/k8s/deployment.yaml: vendor-neutral Kubernetes Deployment and
    Service for the published container image, with credentials read from a
    user-created secret and probes wired to the health endpoints.
  • .pre-commit-config.yaml: local hooks mirroring the blocking CI gates (ruff
    check and format on src/ and tests/, file hygiene, private-key guard).
    Install with uv run pre-commit install; pre-commit is now a dev dependency.
  • .github/pull_request_template.md: prompts contributors for a summary,
    changes, test plan, and rollback note.
  • CI step that regenerates the tool contract fixture and documentation/tools.md
    and fails with the exact regeneration commands when either is stale.

Changed

  • deploy/docker-compose.yml now publishes the plaintext port on loopback
    (127.0.0.1) instead of every interface. That port is published whether or
    not the tls profile is active, so a TLS deployment was still exposing an
    unencrypted listener that would carry LM_HTTP_AUTH_TOKEN in cleartext.
    Caddy is unaffected, since it reaches the service over the compose network.
    Set LM_HTTP_BIND=0.0.0.0 to serve plaintext to other hosts deliberately.
  • deploy/docker-compose.yml no longer defaults LM_CORS_ORIGINS to *.
    Deployments that relied on the implicit wildcard should set an explicit
    origin list rather than restoring *: CORS runs with
    allow_credentials=true, so a wildcard lets any site a browser visits call
    /mcp with the visitor's credentials.
  • With LM_HEALTH_CHECK_CONNECTIVITY on, /health runs the connectivity check
    only for callers that present LM_HTTP_AUTH_TOKEN, or when no token is
    configured; an unauthenticated probe gets the local checks only.
  • CI runs each matrix leg on the Python it names (every leg ran 3.11), installs
    from uv.lock with --locked, pins every action to a commit SHA, and runs with
    a read-only token. A new job installs the huggingface extra with CPU torch and
    runs its tests. Dependabot covers uv, GitHub Actions and Docker weekly.
  • Releases are gated. A tag must be on main and match pyproject.toml, both
    server.json versions, lm_mcp.__version__ and a CHANGELOG section
    (scripts/check_release_tag.py), and lint, the format check, the suite on
    Python 3.11 to 3.13 and the generated files must pass before PyPI publishes; the
    container and the GitHub release wait for PyPI.
  • mypy is a ratchet: CI fails when the error count rises above
    mypy-baseline.txt, in place of a step that could not fail.
  • The container installs from uv.lock with pip --require-hashes, pins its base
    images by digest, drops uv and curl from the runtime image (the healthcheck
    runs on Python), and runs as UID 10001. deploy/k8s/deployment.yaml sets
    runAsUser and runAsGroup to 10001, pins image 4.2.1 in place of latest,
    and mounts an emptyDir at /tmp under its read-only root; compose uses the
    Python healthcheck and a digest-pinned Caddy.