Skip to main content

Python client for the Microsoft Defender for Endpoint API with lazy results and Arrow/Polars output.

Project description

mde-client

Python client for the Microsoft Defender for Endpoint API.

mde-client is organized around a single top-level MDEClient with lazy endpoint wrappers. Endpoint calls return result handles that fetch on first materialization, cache the payload, and can be rendered as Python dictionaries, JSON, PyArrow tables, or Polars DataFrames.

Highlights

  • Client-credentials authentication through MSAL.
  • Lazy endpoint results with shared materialization methods.
  • Coverage across machine inventory, alerts, investigations, authenticated scans, advanced hunting, assessments, remediation, and machine actions.
  • Built-in support for Defender file-export endpoints through ViaFiles.
  • Constructor injection for httpx.Client and msal.TokenCache to keep tests and custom transports straightforward.

Documentation

Use this README for the package overview and quick start.

Install

uv add mde-client

The package also defines optional extras when you want to declare dataframe support explicitly in your environment:

uv add "mde-client[arrow]"
uv add "mde-client[arrow,polars]"

If you are developing inside this monorepo, use the root workflow instead:

uv sync --all-packages --all-groups

Authentication Prerequisites

You need an Azure AD app registration that can use the OAuth 2.0 client-credentials flow against Microsoft Defender for Endpoint.

  • Tenant ID
  • Client ID
  • Client secret
  • Defender application permissions granted to the app registration

MDEClient uses MSAL and the Defender default scope under the hood.

Quick Start

from mde_client import AuthenticationError, MDEClient
from mde_client.endpoints.machines import MachinesQuery

try:
    with MDEClient(
        tenant_id="YOUR_TENANT_ID",
        client_id="YOUR_CLIENT_ID",
        client_secret="YOUR_CLIENT_SECRET",
    ) as client:
        machines = client.machines.get_all(
            MachinesQuery(healthStatus="Active", page_size=500)
        ).to_dicts()

        print(f"Fetched {len(machines)} machines")

        machine = client.machines.get("machine-guid").to_dicts()[0]
        print(machine["computerDnsName"])

        logon_users = client.machines.logonusers("machine-guid").to_dicts()
        print(logon_users[:3])
except AuthenticationError as exc:
    print(f"Authentication failed: {exc}")

Public Imports

The top-level package exports:

  • MDEClient
  • AuthenticationError
  • ViaFiles
  • ViaFilesConfig
  • EmptyExportBlobError

Result Model

Most endpoint methods return a lazy results wrapper rather than an eager list or model.

results = client.machines.get_all(MachinesQuery(healthStatus="Active"))

# No request is made until a terminal method runs.
rows = results.to_dicts()
payload = results.to_json()
table = results.to_arrow()
frame = results.to_polars()

# Drop the cached payload and fetch again on the next terminal call.
fresh_rows = results.refresh().to_dicts()

Behavior to account for:

  • Collection and single-item lookups use the same wrapper style.
  • Pagination is automatic for list endpoints unless you set $top or $skip through a query model.
  • Materialization methods reuse cached data until refresh() is called.
  • to_dicts() is the simplest Python-native representation for downstream code.
  • Write helpers return either lazy result wrappers or bool, depending on whether the underlying API returns an entity payload or an empty success response.

Streaming Arrow IPC for memory-limited runtimes

For very large result sets (some Defender endpoints return millions of rows), the cached terminal methods can exceed tight memory budgets. to_ipc_stream() is an async terminal that streams results as Arrow IPC stream byte chunks, keeping peak memory close to a single record batch — for example when exporting from a 2 GiB Azure Function.

import pyarrow as pa

results = client.machines.get_all()

async for chunk in results.to_ipc_stream(compression="zstd"):
    ...  # forward each chunk to a streaming HTTP response

Unlike the other terminals it is not cached and issues fresh requests on every call. It requires an explicit pyarrow.Schema (from schema= or the wrapper's SCHEMA) because the IPC stream header is written before any rows are fetched. It works for collection pagination, concurrent $top/$skip pagination, single-object responses, and export-backed (files=True) endpoints.

Endpoint Surface

MDEClient exposes endpoint properties for the current Defender surface. The most commonly used groups are:

Property What it covers
machines Machine inventory, machine lookups, related users and alerts, installed software, vulnerabilities, recommendations, machine-scoped actions, and several assessment exports
alerts Alert listing, alert relationships, create-by-reference flows, and batch updates
authenticated_definitions / authenticated_agents Authenticated scan definitions, scanner agents, and scan history workflows
advanced_queries Advanced hunting query execution
software, vulnerabilities, recommendations, remediations, score, baseline_configurations Exposure, remediation, score, and baseline-related datasets
browser_extension, certificate_inventory, device_av_health Assessment inventory and export-backed datasets
files, domain, ips, user, investigations, indicators, library, machine_actions Related entity lookups, response actions, indicator management, and live response library operations

The full property list is defined on MDEClient, but the important pattern is consistent: endpoint methods return a results wrapper or a small success value, and the wrapper APIs stay uniform across endpoint families.

Query Models

Query models inherit from a shared base that supports:

  • page_size mapped to pageSize
  • top mapped to $top
  • skip mapped to $skip
  • OData-style $filter construction from non-null model fields

Defender-specific query fields intentionally preserve upstream API names such as healthStatus, machineTags, and lastSeen instead of normalizing everything to snake_case.

File Exports And ViaFiles

Some Defender endpoints return exportFiles SAS URLs instead of embedding the final dataset in the initial API response. For those endpoints, the result wrappers use ViaFiles internally to:

  • download blobs concurrently
  • stream gzip or plain NDJSON responses
  • parse records incrementally
  • append them into an ArrowRecordContainer

Most callers do not need to use ViaFiles directly because export-backed endpoint wrappers already handle it. Use ViaFiles yourself when you already have export URLs and want to stream them into your own Arrow container with custom tuning through ViaFilesConfig.

EmptyExportBlobError is the internal signal used when a blob returns 200 OK but contains no records. The public downloader retries and then skips confirmed-empty blobs.

Customization And Testing

MDEClient accepts both a custom httpx.Client and an optional MSAL token cache:

import httpx
import msal

client = MDEClient(
    tenant_id="YOUR_TENANT_ID",
    client_id="YOUR_CLIENT_ID",
    client_secret="YOUR_CLIENT_SECRET",
    http_client=httpx.Client(transport=mock_transport),
    token_cache=msal.TokenCache(),
)

This makes it straightforward to test request construction, inject custom transports, or persist tokens outside the default in-memory cache.

Errors And Operational Notes

  • AuthenticationError is raised when MSAL cannot acquire a token.
  • HTTP failures bubble up through httpx status handling.
  • Export-file downloads retry transient failures and raise a runtime error if a blob cannot be fetched successfully; confirmed-empty blobs are skipped after retry logging.
  • The client is a context manager; use with MDEClient(...) as client: when possible so the underlying HTTP session is closed promptly.

License

Apache-2.0

Releasing

mde-client is published to PyPI from this monorepo via a tag-driven GitHub Actions workflow that authenticates with PyPI Trusted Publishing (OIDC, no API tokens).

To cut a release:

  1. Bump version in pyproject.toml.
  2. Add a matching [<version>] - <date> section to CHANGELOG.md and move items out of [Unreleased].
  3. Run just quality locally (includes uv build + twine check).
  4. Merge the version bump and changelog to main. The tag-on-version-bump workflow detects the changed version and automatically creates and pushes the annotated tag mde-client-v<version> (e.g. mde-client-v0.2.0). It is idempotent and skips tags that already exist.
  5. The release workflow triggers on that tag, verifies it matches the pyproject version, builds the sdist and wheel, and publishes to PyPI.
  6. The Docs workflow publishes a versioned documentation snapshot with mike for the same tag. Stable tags move the latest alias and the site root; prerelease tags (rc/a/b) publish a browsable version without changing latest.

Future vendor packages in this monorepo follow the same scheme: <distribution-name>-v<version>.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mde_client-0.2.4.tar.gz (70.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mde_client-0.2.4-py3-none-any.whl (107.2 kB view details)

Uploaded Python 3

File details

Details for the file mde_client-0.2.4.tar.gz.

File metadata

  • Download URL: mde_client-0.2.4.tar.gz
  • Upload date:
  • Size: 70.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mde_client-0.2.4.tar.gz
Algorithm Hash digest
SHA256 79c43fa5028db311e1e9c643d1cd461607932b3667e63b5f8de1c4d88680aef3
MD5 46c3ecd7e5fe0e7cb67b198790e41a19
BLAKE2b-256 cf701388c3294cd8ce9b55859a6f3bf7fac12833868456c2b421074fba1d265f

See more details on using hashes here.

Provenance

The following attestation bundles were made for mde_client-0.2.4.tar.gz:

Publisher: release.yml on ProxayFox/pysec-clients

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mde_client-0.2.4-py3-none-any.whl.

File metadata

  • Download URL: mde_client-0.2.4-py3-none-any.whl
  • Upload date:
  • Size: 107.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mde_client-0.2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 ad59b70b4203c0c19b31814669371a99e85ffceda0705d6ff91fc873257eb4b0
MD5 e18b4f44e0531f7e391c737dad80b8cf
BLAKE2b-256 af743fefaa1ac03e0d8b4a9305cc7393cf0fc2d558e96446515196209b012b2a

See more details on using hashes here.

Provenance

The following attestation bundles were made for mde_client-0.2.4-py3-none-any.whl:

Publisher: release.yml on ProxayFox/pysec-clients

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page