Changelog¶
[1.0.0] — 2026-04-28¶
Added¶
display_hinton every tool response — top-level object with a requiredframestring and optionalprimary_key/titlefields. Tells downstream renderers which visual frame to use for each tool by default, removing the need to infer layout from JSON shape heuristics. The frame for each tool is fixed by the binding table in the spec (seeSYSTEM_PROMPT.mdanddocs/DEVELOPMENT.md):
| Tool | frame |
|---|---|
netbox_search |
hits-by-type |
netbox_get |
table |
netbox_get_all |
table |
netbox_inspect_type |
raw |
netbox_query |
raw |
netbox_aggregate_by_device |
bar-chart |
netbox_count_devices_by |
tree-or-bar |
netbox_device_profile |
card-grid |
netbox_find_ip_references |
trio |
netbox_trace_path |
timeline-or-graph |
netbox_get_available |
table |
primary_key is included where a sensible row identifier exists ("id" for table
frames, "device.id" for card-grid and bar-chart frames, "parent.id" for tree-or-bar).
title is derived from tool arguments where meaningful (e.g. "Top devices by
tunnel_terminations" for netbox_aggregate_by_device, "Devices grouped by dcim.region"
for netbox_count_devices_by).
The display_hint is injected via result.setdefault(...) — if a service already
returns a display_hint, the service value is preserved (forward-compat precedence
rule, consistent with the tool_name / elapsed_ms pattern).
The frame vocabulary (FRAME_VOCABULARY: frozenset[str]) is defined as a single
module-level constant in src/netbox_agent_mcp/tools/_display_hint.py and is imported
by contract tests to assert the vocabulary equals the binding set exactly.
Changed — BREAKING¶
netbox_trace_pathandnetbox_get_availablereturn type changed fromlisttodict[str, Any]. The returned list is now nested under a"path"key (netbox_trace_path) or a"results"key (netbox_get_available), so the response carries the same top-level envelope (tool_name,elapsed_ms,display_hint) as every other tool. This is the only breaking change in the release.
Migration:
# before
hops = netbox_trace_path(device="r1", interface="ge-0/0/0")
for hop in hops:
...
free = netbox_get_available(object_type="ipam.prefix", parent_id=42)
for prefix in free:
...
# after
result = netbox_trace_path(device="r1", interface="ge-0/0/0")
for hop in result["path"]:
...
result = netbox_get_available(object_type="ipam.prefix", parent_id=42)
for prefix in result["results"]:
...
The shape of every other tool's response is unchanged for fields that already
existed; the tool_name / elapsed_ms / display_hint additions are additive.
Because this changes the return type of two public tools, the next release is a
SemVer MAJOR bump (1.0.0).
[0.2.0] — 2026-04-28¶
Added¶
tool_nameon every tool response — top-level string field echoing the called tool's__name__(e.g."netbox_get"). Lets renderers, audit drawers, and right-rail cards identify which tool produced a response without parsing the MCP call path or relying on bridge metadata.elapsed_mson every tool response — top-level integer field recording server-side wall-clock execution time in milliseconds (measured viatime.perf_counter_nsto avoid CPU-time skew). More accurate than browser-measured fetch latency, which includes mcpo bridge overhead and network round-trip. Intended for timing strips and per-call latency analysis in Phase 4's right-rail card.
Both fields are injected by a new with_response_envelope decorator applied at tool
registration time in server.py. The decorator is transparent: it uses functools.wraps
to preserve the original signature and docstring that FastMCP introspects for the MCP schema,
never touches tool internal logic, and honours tool-provided values — if a tool already
returns tool_name or elapsed_ms in its dict, those values are preserved unchanged.
These are additive, optional fields; no existing response keys are modified.
[0.1.10] — 2026-04-23¶
Fixed¶
- Scrubbed a multicast CIDR that had been copied verbatim from a user debug trace into
tests/unit/schema/test_ip_scope.py. Replaced with a generic TEST-NET-equivalent (239.1.2.0/24). Git history was rewritten (force-push) to remove the value from every commit + commit message; the v0.1.9 tag was re-pointed at the clean SHA; the Actions artifacts containing the leaked sdist were deleted. 0.1.9 should be treated as recalled — the PyPI sdist contains the leaked test fixture and should be yanked / deleted by the maintainer.
[0.1.9] — 2026-04-23 (YANKED)¶
Added¶
ip_scopefilter key onnetbox_get/netbox_get_all— post-filtersipam.ipaddress/ipam.prefix/ipam.iprangeresults by address classification using Python'sipaddressstdlib. Values:public,private(RFC 1918 + CGNAT + IPv6 ULA),documentation(RFC 5737 + RFC 3849),loopback,link-local,multicast,reserved. Response includes anip_scope_filterfield with the before/after counts;total_countreflects the pre-filter NetBox count so callers can detect the delta. Invalid scope values and non-IP object types raiseValueErroreagerly. Newschema/ip_scope.pymodule + 12 tests covering every scope class (IPv4 + IPv6) and the dropped-for-narrow-scope-junk-records edge case.scope_under_site_idfilter key onnetbox_getforipam.prefix/ipam.vlan— closes the NetBox 4.x polymorphic-scope gap wheresite_id=Non a prefix only matches records whosescope_type == "dcim.site"and silently misses records scoped to Locations nested under that site. The magic key resolves under the hood: fetches the site's locations, then unions direct-site-scoped and location-scoped queries and deduplicates. Response includes ascope_resolutionblock with per-sub-query match counts for transparency. Not supported onnetbox_get_all(each sub-query's total is independently capped; usenetbox_getwith paging instead). 7 tests covering the union, dedup, empty-locations fast path, and rejection on non-scoped types.
Fixed¶
netbox_get/netbox_get_alldocstrings now document both magic filter keys alongside the existing"q"/__in/__icforms.SYSTEM_PROMPT.mdintent table adds explicit rows for "public/private IPs" and "everything scoped under site X" so the agent routes to these filters instead of hand-rolling subnet math or writing off location-scoped prefixes as missing.
[0.1.8] — 2026-04-23¶
Added¶
- New tool
netbox_count_devices_by(parent_type, top_n=0)— answers "device counts broken down by region / site-group / site / location / role / tenant" in a single complete call, eliminating the small-model failure mode ofnetbox_get("dcim.site", limit=100)+ truncated client-side aggregation on larger deployments. Picks the right strategy per parent type:dcim.site/dcim.location/dcim.devicerole/tenancy.tenantuse NetBox's pre-computeddevice_countannotation (direct scan);dcim.regionanddcim.sitegroupsumsite.device_countacross every site via a full paginated scan. For region/sitegroup the response includes anunassigned_devicesbucket (devices in sites with no region/group) so totals reconcile against the global device count. DeviceCountByParentService— new service + 7 tests covering the real-world failure scale (403+ sites, full pagination, region/sitegroup rollup, direct-count path, top-N semantics preserving totals, unassigned handling).
Fixed¶
- Removed the misleading "order by
-device_count" breadcrumb for regions/sitegroups. TheSYSTEM_PROMPT.mdintent row for "Top N parents by pre-computed count" now explicitly scopes to the types that actually carry the annotation (dcim.site,dcim.location,dcim.devicerole,tenancy.tenant) and routes region/sitegroup to the new tool. When an agent asksordering="-device_count"ondcim.regionordcim.sitegroup, thefields_dropped_hintnow explicitly tells them to usenetbox_count_devices_byinstead of silently dropping the field.
[0.1.7] — 2026-04-23¶
Added¶
- Region and site-group traversal now surfaced to agents. NetBox's
region_idfilter ondcim.device/dcim.rack/circuits.circuit/ipam.prefix/ipam.vlan/virtualization.virtualmachinewalks{object}.site.regionnatively and automatically includes descendant regions in the tree — but nothing in the server was telling the agent this. After a region search, the agent previously got onlysites_in_these_regionsand had to chain region → sites → devices in three calls. Nowcompute_suggested_filtersemits the full traversal suite (devices_in_these_regions,racks_in_these_regions,circuits_in_these_regions,prefixes_in_these_regions,vlans_in_these_regions,vms_in_these_regions), anddcim.regioncarries atype_guidancehint explaining the pattern. - Same treatment for
dcim.sitegroup(functional grouping that cross-cuts geography): newdevices_in_these_sitegroups/racks_in_these_sitegroupsvia thesite_group_idtraversal filter, plus guidance. SYSTEM_PROMPT.mdintent table adds a dedicated row for "devices / racks / circuits / prefixes / VLANs / VMs in region X" showing the one-call pattern.
[0.1.6] — 2026-04-23¶
Fixed¶
netbox_find_ip_referencesnow surfaces NetBox 4.x's polymorphicscopeon containing prefixes and IP ranges. Previously the tool's_distill_containerpulled only the legacysiteFK, which NetBox 4.x populates as a backward-compat virtual field only whenscope_type == "dcim.site". When a prefix was scoped to a Location, Region, or SiteGroup (e.g. a building floor, a metro region),sitewas null and the agent reported "no site associated" even though the real location was right there inscope. The distilled container now exposes bothscope_type+scope(preferred) andsite(legacy/backward-compat), and thesummary.hintlabels the scope level ("locationX" vs. "siteY") so agents don't conflate hierarchy tiers.netbox_find_ip_referencesdocstring now calls out scope-vs-site semantics explicitly, anddocs/SYSTEM_PROMPT.mdinstructs agents to readscopefirst and fall back tositeonly whenscopeis null (pre-4.x NetBox).
[0.1.5] — 2026-04-23¶
Docs (agent-facing)¶
netbox_getfilter grammar now documents the"q"free-text key. Example:netbox_get("dcim.site", {"q": "london"}, select=[...], ordering="-device_count")— same?q=semantics asnetbox_search, scoped to one type, with agent-chosen field projection and order-by. This is the efficient idiom for "find all [sites / devices / VLANs / circuits / racks] matching X" when the type is known.netbox_searchdocstring reworked to explicitly steer agents towardnetbox_getwhen the type is known and richer columns are needed, and to keepnetbox_searchfor type-unknown discovery and for responses wheresuggested_filters/ per-type truncation signals matter.SYSTEM_PROMPT.mdintent table updated with a dedicated row for scoped searches by type.
No code changes — this is pure prompt engineering. Agents running 0.1.4 with the old prompt already had this capability; 0.1.5 just makes them discover it reliably.
[0.1.4] — 2026-04-23¶
Added¶
netbox_device_profilenow accepts afields=[...]projection list that slims the top-leveldevicerecord and every item in every section down to just the requested fields (id+displayare always kept as navigation anchors). Supports dotted paths like"site.name"for nested projection. Combined with batcheddevice_ids+max_per_category, agents can now get N devices' worth of data with surgical token cost — e.g. for a "devices at site X" summary table,fields=["name","role","status","primary_ip4.address"]drops payload size by ~10x vs. the default brief shape.- Docstring +
docs/SYSTEM_PROMPT.mdupdated to instruct agents to always passfieldsalongsidedevice_idsfor batched calls.
[0.1.3] — 2026-04-23¶
Added¶
netbox_device_profilenow accepts a list of device IDs for batched, parallel profiling. Callnetbox_device_profile(device_ids=[D1, D2, ...])once instead of N sequentialdevice_id=Dicalls. Up to 50 devices per batch, fanned out across 5 parallel workers, with per-device failure isolation (one bad device doesn't abort the batch — it gets anerrorfield in the response while the rest return normally). Response shape:{profiles: [...], meta: {requested, returned, failed}}; profile order preserves caller'sdevice_idsorder.- Docstring +
docs/SYSTEM_PROMPT.mdupdated to instruct agents to prefer the batch form whenever a list of devices is on hand (e.g., right afternetbox_get('dcim.device', ...)ornetbox_aggregate_by_device).
[0.1.2] — 2026-04-23¶
Fixed¶
- Replaced real-world IP addresses, site codes, and device hostnames that had been copied verbatim from a user-provided debug trace into the 0.1.1 docstring + tests. All now use RFC 5737 documentation addresses (
192.0.2.x,198.51.100.x,203.0.113.x) and generic names (site-a,site-b,router-edge-a). 0.1.1 should be treated as recalled — it contains placeholder but non-RFC-5737 data that resembles real infrastructure and should not be distributed.
[0.1.1] — 2026-04-23 (YANKED)¶
Fixed¶
netbox_find_ip_referencesnow locates addresses inside tracked prefixes and IP ranges. Previously the tool only checked per-hostipam.ipaddressrecords and free-text descriptions; agents reported "IP not found" for addresses that lived inside a known subnet (the common case in deployments that track prefixes densely but host records sparsely). The tool now also queriesipam/prefixes?contains=<ip>andipam/ip-ranges?contains=<ip>, returns the enclosing records undercontaining_prefixes/containing_ip_rangeswith site + role + description, and updatessummary.hintto steer the agent at the containing record's site when there's no per-host match.- Updated tool docstring and
docs/SYSTEM_PROMPT.mdto call out the multi-layer lookup and explicitly forbid "IP not found" answers beforecontaining_prefixeshas been read.
Fixed¶
netbox_find_ip_referencesnow locates addresses inside tracked prefixes and IP ranges. Previously the tool only checked per-hostipam.ipaddressrecords and free-text descriptions; agents reported "IP not found" for addresses that lived inside a known subnet (the common case in deployments that track prefixes densely but host records sparsely). The tool now also queriesipam/prefixes?contains=<ip>andipam/ip-ranges?contains=<ip>, returns the enclosing records undercontaining_prefixes/containing_ip_rangeswith site + role + description, and updatessummary.hintto steer the agent at the containing record's site when there's no per-host match.- Updated tool docstring and
docs/SYSTEM_PROMPT.mdto call out the multi-layer lookup and explicitly forbid "IP not found" answers beforecontaining_prefixeshas been read.
Docs¶
- MkDocs Material site auto-built + deployed to GitHub Pages at https://magicboxlab-ai.github.io/netbox-agent-mcp/. Adds
pyproject.toml[dependency-groups].docs,mkdocs.yml, a landing page + thin wrappers for root-level docs, and.github/workflows/pages.yml. README now carries PyPI + Docs badges.
[0.1.0] — 2026-04-22¶
First public release. Agentic MCP server for NetBox with ten read-only tools:
netbox_search, netbox_get, netbox_get_all, netbox_aggregate_by_device,
netbox_device_profile, netbox_find_ip_references, netbox_trace_path,
netbox_get_available, netbox_inspect_type, netbox_query (raw GraphQL).
Docs¶
- Repo-level AI-assistant guide (git-ignored, personal-to-maintainer).
docs/DEVELOPMENT.mdconsolidates design, implementation, testing, and extending in one developer guide.
Repository hygiene¶
- Scrubbed realistic-looking hostnames and IPs from tests + one docstring (RFC 5737 addresses + generic
router-a/router-bplaceholders). .pre-commit-config.yaml(gitleaks + ruff + standard hooks) and.gitleaks.tomlwith an allowlist for syntheticnbt_*test tokens.SECURITY.mdwith a private vulnerability-reporting policy.CONTRIBUTING.mdandCODE_OF_CONDUCT.md(Contributor Covenant v2.1 by reference)..github/workflows/ci.yml— ruff + pytest (3.11 / 3.12 / 3.13) + gitleaks on push + PR..github/workflows/publish.yml— tag-triggered PyPI publish via trusted publishing (OIDC, no tokens)..github/dependabot.yml+dependabot-auto-merge.yml— weeklyuv+github-actionsupdates, patch/minor auto-merged on green CI, majors flagged for manual review.- Issue templates (bug / feature) and PR template.
pyproject.tomlauthors,urls,keywords, and PyPI classifiers.- README badges: CI, Python versions, license, pre-commit, Ruff.
- README install path leads with
uvxanduv tool install;git clonemoved to a Development install section.
Code quality¶
__insuffix-stripping filter translation centralized inschema/filter_builder.filters_to_rest_params;QueryServiceandDeviceAggregationServiceshare it.- Removed unreachable
last_exc/assertdead code fromRestClient._requestandGraphQLClient.query. server.maincloses the sharedhttpx.Clienton shutdown;build_servicesreturns(registry, http).- Deleted unreferenced GraphQL-first helpers:
schema/select_compiler.pyand the GraphQL portion ofschema/filter_builder.py(SUFFIX_MAP,build_filter_args, support fns).