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.Clientandmsal.TokenCacheto keep tests and custom transports straightforward.
Documentation
Use this README for the package overview and quick start.
- For the structured docs set, start at ../../docs/mde_client/index.md.
- For a first-success walkthrough, use ../../docs/mde_client/tutorials/get-started.md.
- For API lookup, use ../../docs/mde_client/reference/index.md.
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:
MDEClientAuthenticationErrorViaFilesViaFilesConfigEmptyExportBlobError
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
$topor$skipthrough 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.
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_sizemapped topageSizetopmapped to$topskipmapped to$skip- OData-style
$filterconstruction 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
AuthenticationErroris raised when MSAL cannot acquire a token.- HTTP failures bubble up through
httpxstatus 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
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:
- Bump
versioninpyproject.toml. - Add a matching
[<version>] - <date>section toCHANGELOG.mdand move items out of[Unreleased]. - Run
just qualitylocally (includesuv build+twine check). - Commit the version bump and changelog on
main. - Tag the commit
mde-client-v<version>(e.g.mde-client-v0.1.0) and push the tag. The release workflow verifies the tag matches the pyproject version, builds the sdist and wheel, and publishes to PyPI.
Future vendor packages in this monorepo follow the same scheme:
<distribution-name>-v<version>.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mde_client-0.1.4.tar.gz.
File metadata
- Download URL: mde_client-0.1.4.tar.gz
- Upload date:
- Size: 66.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8f21819732e9971bbf59739af83009b6613c05215545c6ea9260ad8fa347e6df
|
|
| MD5 |
ece4389105def7b41462ff6efc4587ec
|
|
| BLAKE2b-256 |
2931f73a4531aa2e0409b803a7085011f0f7f1b96b60422238402131a0fd17c1
|
Provenance
The following attestation bundles were made for mde_client-0.1.4.tar.gz:
Publisher:
release.yml on ProxayFox/pysec-clients
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mde_client-0.1.4.tar.gz -
Subject digest:
8f21819732e9971bbf59739af83009b6613c05215545c6ea9260ad8fa347e6df - Sigstore transparency entry: 1707191222
- Sigstore integration time:
-
Permalink:
ProxayFox/pysec-clients@88a12e59145e07fa4d0298143faca07ab537ac7a -
Branch / Tag:
refs/tags/mde-client-v0.1.4 - Owner: https://github.com/ProxayFox
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@88a12e59145e07fa4d0298143faca07ab537ac7a -
Trigger Event:
push
-
Statement type:
File details
Details for the file mde_client-0.1.4-py3-none-any.whl.
File metadata
- Download URL: mde_client-0.1.4-py3-none-any.whl
- Upload date:
- Size: 104.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8ffe71258f81c800b6fea20bb9aabdec7fbe3503c5b67b675f8f46bac89622b3
|
|
| MD5 |
c438953ab18c0ba9b2e756c323e922c8
|
|
| BLAKE2b-256 |
89b80b36737df7981ef8c13fc78675d022b978421ad655cb4bfd770d75a1744c
|
Provenance
The following attestation bundles were made for mde_client-0.1.4-py3-none-any.whl:
Publisher:
release.yml on ProxayFox/pysec-clients
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mde_client-0.1.4-py3-none-any.whl -
Subject digest:
8ffe71258f81c800b6fea20bb9aabdec7fbe3503c5b67b675f8f46bac89622b3 - Sigstore transparency entry: 1707191246
- Sigstore integration time:
-
Permalink:
ProxayFox/pysec-clients@88a12e59145e07fa4d0298143faca07ab537ac7a -
Branch / Tag:
refs/tags/mde-client-v0.1.4 - Owner: https://github.com/ProxayFox
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@88a12e59145e07fa4d0298143faca07ab537ac7a -
Trigger Event:
push
-
Statement type: