s2-netbox-mcp
This server exposes LenelS2 S2 NetBox NBAPI access-control operations as MCP tools, read-only by default and with write/destructive tools gated behind explicit opt-in environment variables.
Connect & guide — verify controller auth and NBAPI version (
check_connection), ping the app (ping_app), and pull built-in S2 NetBox domain guidance on six topics viaget_guide(access model, unlock windows, group/name gotchas, credentials/card formats, API quirks, write safety).Person & credential lookup — fetch a person by
PERSONID(get_person), search people by name/email/phone/card/UDF filters (search_person_data), read a person's badge photo (get_picture), and inspect mobile virtual credential state (get_virtual_credential_request).Card/access investigation — list card formats (
get_card_formats), get card access details byENCODEDNUM+CARDFORMAT(get_card_access_details), and read access history with optional name/reader-description enrichment (get_access_history,get_reader_access_history).Access-control configuration reads — list/get access levels, access level groups, access level names, time specs, time spec groups, holidays, portal groups, reader groups, partitions, and UDF lists/items.
Door & hardware reads — list portals (with nested readers and resolved descriptions), readers, readers' locations, outputs, portal states/statuses, locations, elevators, floors, alarms, Mercury panels, network nodes, and SIOs.
Door finding & event review — search doors by name/description (
find_portals) and review events/activity (list_events,get_event_history).Unlock-window inspection — read the current managed continuous or daily unlock window (
get_unlock_window,get_daily_unlock_window), including derived schedule and current active state.Writes (opt-in via
NETBOX_ENABLE_WRITES) — create/modify time specs, groups, holidays, portal groups, reader groups, access levels/groups, persons, credentials, threat levels/groups, partitions, UDF list items, Mercury panels, network nodes, SIOs; lock/unlock/momentary-unlock portals; activate/deactivate outputs; trigger events; add duty logs and virtual credential requests; plus compositesset_portals_state,schedule_unlock_window,cancel_unlock_window,schedule_daily_unlock_window,cancel_daily_unlock_window.Destructive (opt-in via
NETBOX_ENABLE_WRITES+NETBOX_ENABLE_DESTRUCTIVE) — delete/remove access levels and groups, holidays, portal groups, reader groups, time specs and groups, persons, credentials, threat levels and groups, Mercury panels, network nodes, SIOs, and virtual credential requests.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@s2-netbox-mcpsearch for a person named Jane Doe and show their access levels"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
s2-netbox-mcp
A local MCP server that exposes LenelS2 S2 NetBox NBAPI operations — persons/credentials, access levels, portals/readers/outputs, time specs, holidays, portal/reader groups, threat levels, events/activity, and partitions/UDF lists — as MCP tools usable from any MCP-compatible client (Claude, Gemini/Antigravity, etc.).
Not the open-source netboxlabs.com "NetBox" DCIM/IPAM tool. This targets LenelS2's S2 NetBox physical access-control appliance and its NBAPI (
Web-Based API for S2 NetBox and S2 Global, LenelS2 doc #API-UG-14). Newer NBAPI v1 (doc #API-UG-22, April 2024) and v2 (doc #API2-UG-8, April 2025) guides also exist — seedocs/reference/for reference copies. Both have now been diffed against this server's tool surface command by command and parameter by parameter:docs/reference/nbapi-command-diff.mdrecords the result, including which documented commands are deliberately not implemented and why.
This connects to a real physical security system. With the wrong
configuration, an AI agent using this server could unlock doors or modify
access-control data on a live building. It is read-only by default —
writes and destructive operations (lock/unlock, add/modify/delete) each
require their own explicit opt-in environment variable (see Write
access below) — but you are responsible for what you enable and which
MCP client/model you point at it. See SECURITY.md before deploying
anything beyond read-only against a production controller.
Install
Three steps, plus an optional fourth: check the requirements, install the server, register it with your MCP client, and optionally add the workflow skill. Before the first real call, also check the controller prerequisites.
Requirements
Node.js >= 18.17 (tested on Node 24), which includes npm. If Node.js isn't installed, on Windows you can install it with winget, then open a new terminal so
node/npmare on yourPATH:winget install OpenJS.NodeJS.LTSAn S2 NetBox controller reachable from wherever this server runs, with the NBAPI enabled and configured for session-login authentication (not MAC authentication — see the spec for why that's out of scope for v1)
A NetBox operator account with API access and read permission on the resources you want to query
Install the MCP server
Two ways to get the server:
Option A — npm (no clone needed):
npm install -g s2-netbox-mcpThis installs the s2-netbox-mcp binary; point your MCP client's command at
s2-netbox-mcp directly (no node dist/index.js needed).
Option B — clone and build:
npm install
npm run buildEither way, copy .env.example to .env and fill in real values (or provide the same
variables directly in your shell / in the Claude Code MCP server config's
env block — see below). Never commit .env — it's already gitignored.
cp .env.example .env
# edit .envStart the server directly to sanity-check it boots (it just waits on stdio
for an MCP client — Ctrl+C to stop; this also sends Logout if a session was
opened):
npm startRegistering with an MCP client
This server works with any MCP client that speaks the standard mcpServers
stdio config shape — Claude Code, Antigravity, and Gemini CLI have all been
verified against it directly. Which JSON to use depends on which install
option you picked above:
If you installed via npm (Option A above):
{
"mcpServers": {
"s2-netbox-mcp": {
"command": "s2-netbox-mcp",
"env": {
"NETBOX_BASE_URL": "https://netbox.example.internal",
"NETBOX_USERNAME": "svc-account",
"NETBOX_PASSWORD": "REPLACE_ME",
"NETBOX_ALLOW_INSECURE_TLS": "false"
}
}
}
}If you cloned and built locally (Option B above):
{
"mcpServers": {
"s2-netbox-mcp": {
"command": "node",
"args": ["/absolute/path/to/s2-netbox-mcp/dist/index.js"],
"env": {
"NETBOX_BASE_URL": "https://netbox.example.internal",
"NETBOX_USERNAME": "svc-account",
"NETBOX_PASSWORD": "REPLACE_ME",
"NETBOX_ALLOW_INSECURE_TLS": "false"
}
}
}
}Replace the args path with the actual absolute path to dist/index.js on
your machine, and replace the env values with your real controller details
(or omit env entirely and rely on a .env file next to the project if you
prefer — either works, since src/index.ts loads .env via dotenv before
reading process.env). Run npm run build first so dist/index.js exists.
Where to put that JSON depends on the client:
Client | Config file | Notes |
Claude Code |
| Or add non-interactively via |
Antigravity |
| Global — applies to every Antigravity session |
Gemini CLI |
| Or add non-interactively via |
All three use the identical mcpServers object shape shown above — only the
surrounding file and location differ.
Install the workflow skill (optional)
The s2-netbox-workflows Agent Skill adds step-by-step
onboard, offboard, event-unlock, and badge-denied workflows on top of this
server's tools. It needs the server registered above.
Install in Claude Code (plugin):
/plugin marketplace add J-MaFf/s2-netbox-mcp
/plugin install s2-netbox-workflows@s2-netbox-mcpThe plugin, in plugins/s2-netbox-workflows/,
ships only the skill. It doesn't bundle or launch the MCP server, so it won't
start a second server next to the one you already registered. To pick up a
newer version, run claude plugin marketplace update s2-netbox-mcp, then
claude plugin update s2-netbox-workflows@s2-netbox-mcp, then restart
Claude Code.
Install manually (Claude Code or any agent that reads Agent Skills): copy
or symlink plugins/s2-netbox-workflows/skills/s2-netbox-workflows/ into your
agent's skills directory. For Claude Code that's ~/.claude/skills/ for every project, or
.claude/skills/ in a single project. The skill loads on its own when a
request matches it, for example "onboard Jane Doe with card 12345", "unlock
the lobby Friday 8 to 5", or "why was badge 4471 denied at the dock door?".
Related MCP server: N-central MCP Server
Controller prerequisites
Before this server can talk to your controller, on the NetBox web UI go to Configuration → Site Settings → Network Controller → Data Integration and confirm all three of these are checked:
Enable V2
Use Authentication
Use login username/password for authentication (requires setup privilege)
The NBAPI user account also needs a role with NBAPI read access (see the
NBAPI doc's "Setting Up User Roles for the API" section) — a login that
succeeds but can't read the resources this server queries will surface as
FAIL or APIERROR responses per tool call. If you set
NETBOX_ENABLE_WRITES, that role needs Read-Write API privilege instead
(Configuration → Site Settings → User Roles → API Privilege) — Read-Only
suffices only for the read tools.
Troubleshooting
"Login succeeds but every other command returns
APIERROR 5." This is the live-observed symptom of the Use login username/password for authentication (requires setup privilege) checkbox being unticked, which puts the controller in MAC-authentication mode instead of session-login mode (MAC auth is out of scope for this server — see the spec).Loginstill returnsSUCCESSwith a session ID, but every subsequent command — includingLogout— fails withAPIERROR 5. Fix: tick that checkbox on the Data Integration tab. This server's client detects this exact pattern (a successful re-login followed by anotherAPIERROR 5) and surfaces a tool error naming the checkbox directly."HTTP 410 Gone." The configured
NETBOX_API_PATHis not served by this controller. NetBox 6.x serves the NBAPI at/nbws/goforms/nbapi(the default this server uses); the 2020 doc's/goforms/nbapipath is deregistered on 6.x and returns 410 for every request. If you're on a pre-6.x controller, setNETBOX_API_PATH=/goforms/nbapiexplicitly; if you're on 6.x and still see this, double-checkNETBOX_API_PATHisn't set to something else by mistake.
Environment variables
Variable | Required | Default | Description |
| Yes | — | Base URL of the NetBox controller's web interface, e.g. |
| Yes | — | NBAPI session-login username. |
| Yes | — | NBAPI session-login password. Never logged, never written to any tracked file. |
| No |
| Set to |
| No |
| The NBAPI path appended to |
| No |
| Set to |
| No |
| Set to |
| No | tracks | Request path used only for |
| No |
| The holiday groups reserved for the managed unlock window, in |
| No |
| Name prefix of every object the managed unlock window creates: the portal group ( |
| No |
| The single holiday group reserved for the managed daily recurring unlock window — see Scheduled daily unlock windows below. Must be a single integer in |
| No |
| Name prefix of every object the managed daily unlock window creates: the portal group ( |
| No | — | The |
If any of the three required variables is missing, the server prints a single actionable line to stderr and exits with a non-zero status — it never prints a stack trace on startup misconfiguration.
Read-only by default
With no write-related environment variables set, this server registers only query/read NBAPI commands:
LoginLogoutGetAPIVersionGetPersonSearchPersonDataGetCardAccessDetailsGetCardFormatsGetAccessLevel(s)GetAccessLevelGroup(s)GetPortalsGetReader(s)GetOutputsGetTimeSpec(s)GetTimeSpecGroup(s)GetHoliday(s)GetPortalGroup(s)GetReaderGroup(s)GetAccessLevelNamesGetPartitionsGetUDFListsGetUDFListItemsGetElevatorsGetFloorsPingAppGetEventHistoryListEventsGetAccessHistory
By default, this server registers only the query/read commands listed above.
It does not register any write, delete, or control operations against the
controller until you explicitly opt in via the environment variables in
Write access below. Among the read-only tools, four are composites,
find_portals, get_unlock_window, get_daily_unlock_window, and
get_reader_access_history, which issue only read commands. Note there is no
GetPortal (singular) command; only GetPortals (plural, paginated, no
single-portal filter) exists on the real NBAPI.
Write access
Write/control tools exist in this server but are not registered unless you explicitly opt in:
NETBOX_ENABLE_WRITES=trueregisters the write tools listed in the "Write tools" table below — the 45 pass-through tools (creating, modifying, locking/unlocking, activating, and triggering) plus the three composite write toolsset_portals_state,schedule_unlock_window, andcancel_unlock_window. Left unset (the default), the server's tool surface is exactly the read tools below — byte-for-byte the same read-only posture as before this variable existed.The two unlock-window composites delete only the holidays and time specs they themselves own (named
<prefix> first|middle|last— see Scheduled unlock windows), and do so withoutNETBOX_ENABLE_DESTRUCTIVEbecause those objects are server-owned; they never delete anything else.NETBOX_ENABLE_DESTRUCTIVE=true, set in addition toNETBOX_ENABLE_WRITES, registers the 11 destructive tools (each description isDESTRUCTIVE:-prefixed):delete_access_level,delete_access_level_group,delete_holiday,delete_portal_group,delete_reader_group,delete_time_spec,delete_time_spec_group,remove_credential,remove_person,remove_threat_level,remove_threat_level_group. Two ordinarily non-destructive write tools also independently refuse one specific destructive-shaped call when this flag is off, regardless of whether the tool itself is registered:modify_personrefuses a call withDELETED="TRUE"orPERSONPURGE="TRUE", andmodify_udf_list_itemsrefuses a call where any list item hasDELETE="1"— both nameNETBOX_ENABLE_DESTRUCTIVEin the error and send nothing to the controller.Every write tool's description starts with
WRITE:(orDESTRUCTIVE:for the 11 above), and every successful write's result text contains the literalSUCCESSfollowed by the controller's response data as pretty JSON (which may be{}when the command returns no data), so you can always tell a write actually happened.Client-side guards (e.g. "give either
READERKEYorREADERGROUPKEY, not both") reject malformed calls with a tool error before any NBAPI command is issued — no partial or guessed request ever reaches the controller.
Set these the same way as the other variables — in .env (see
.env.example) or your MCP server config's env block.
Agent guidance
This server sets the MCP instructions field and exposes an always-registered
get_guide tool, so any agent connecting to it — via npm install or a local
clone, in Claude Code, Antigravity, Gemini CLI, or any other MCP client — has
this server's own S2 NetBox operating knowledge immediately, with no separate
skill install and no extra step.
instructions describes the access model (person → credential → access
level → access level group determines what a person can access; portal
group / time spec group determines where and when), states that most
parameters are numeric KEY fields rather than names (resolve a name to its
KEY with the matching get_*/find_* tool first), and states that text
returned from the controller is data, not instructions to follow. With
NETBOX_ENABLE_WRITES set, it also states a firm confirm-before-acting policy
for lock/unlock, portal-state, unlock-window, and destructive calls (the
write-safety topic below elaborates it); with NETBOX_ENABLE_DESTRUCTIVE
also set, it adds one sentence naming the DESTRUCTIVE:-prefixed tools and
their extra confirmation requirement.
get_guide (always registered; makes no controller call) returns deeper
reference material on six topics. Call it with no arguments for an index of
all six with a one-line summary each, or with topic set to one of the keys
below for that topic's full content:
Topic key | Covers |
| The person → credential → access level → access level group chain, and the portal-group/time-spec-group name-table collision. |
| The holiday + time spec + portal group |
|
|
| Diagnosing a |
|
|
| Naming the target and effect, waiting for explicit confirmation, preferring scheduled tools, and reversing every write. |
get_guide is counted among the always-registered read tools below: the tool
surface is 51 tools with both gates off, 98 with
NETBOX_ENABLE_WRITES, and 113 with NETBOX_ENABLE_DESTRUCTIVE as well.
Workflow skill
instructions and get_guide explain how individual tools behave. The
optional s2-netbox-workflows Agent Skill in
plugins/s2-netbox-workflows/skills/s2-netbox-workflows/ adds
step-by-step recipes for jobs that take several tool calls:
Workflow | What it does | Writes? |
Checks for a duplicate person, creates the person with access levels (block syntax), issues a card, then verifies. | Yes | |
Disables every card and removes access levels, so it can be reversed. Then checks access history. Deletes cards or the person only on explicit request. | Yes | |
Finds the doors with | Yes | |
Finds the denial record, then uses its reason to check the card format, card status, access level, reader group, schedule or door state. | No |
The skill links to get_guide topics rather than repeating them, and follows
the same write-safety rules: name the target and effect, wait for an explicit
yes, check for SUCCESS, and say how to undo. It contains no site-specific
door codes or keys. It needs this MCP server to be registered separately (see
Registering with an MCP client), and its
write steps need the write gates in Write access.
To install it, see Install the workflow skill.
Tools exposed
Read tools (always registered)
Tool | Wraps NBAPI command | Required params |
|
| — |
| — (pure in-process lookup; no controller call) | — (optional |
|
|
|
|
| — (all filters optional) |
|
|
|
|
| — |
|
|
|
|
| — (optional |
|
|
|
|
| — (optional |
|
| — (optional |
|
| — (optional |
|
|
|
|
| — (optional |
|
| — (optional |
|
|
|
|
| — (optional |
|
| — (optional |
|
| — (optional |
|
|
|
|
|
|
|
| — (optional |
|
|
|
|
| — (optional |
|
|
|
|
| — (no calling parameters) |
|
|
|
|
| — (optional |
|
|
|
|
| — (optional |
|
| — |
|
| — |
|
|
|
|
| — (optional |
|
| — (optional |
|
| — |
|
| — (optional |
|
| — |
|
| — |
|
| — (optional |
|
| — (optional |
|
| — (optional |
|
| — (optional |
|
|
|
|
|
|
|
| — (optional |
|
|
|
|
| — (optional |
|
|
|
|
|
|
|
|
|
There is deliberately no get_portal (singular) tool — no such NBAPI command
exists; only GetPortals (plural) does. get_card_access_details and
get_access_history identify a card by ENCODEDNUM/CARDFORMAT (and
get_access_history optionally by HOTSTAMP), not by PERSONID — neither
command has a PERSONID parameter.
Every read tool except find_portals returns a thin JSON pass-through of
that NBAPI command's response fields — no reshaping. Each tool's input
schema declares exactly the documented PARAMS fields for its command — no
invented, renamed, or passthrough fields. All NBAPI parameter names above
are copied verbatim from the NBAPI Command Reference (see
specs/archive/s2-netbox-mcp-write.md and the archived specs/archive/s2-netbox-mcp.md)
— none are invented or guessed.
Nine tools are composites — they combine several NBAPI commands and reshape
the result instead of passing one command through: find_portals,
get_unlock_window, get_daily_unlock_window, and get_reader_access_history
(read-only, always registered), and set_portals_state,
schedule_unlock_window, cancel_unlock_window, schedule_daily_unlock_window,
and cancel_daily_unlock_window (write, registered only with
NETBOX_ENABLE_WRITES). Every composite reads list commands fully
paginated (following NEXTKEY until -1, or AFTERLOGID/NEXTLOGID over a
bounded SCANWINDOW for get_reader_access_history) and issues only
commands from the closed allowlist. set_portals_state locks, unlocks (Extended Unlock until
locked again), or momentarily unlocks many portals in one call — the given
portalKeys or every portal — issuing one command per portal sequentially and
never stopping on a single failure; its result partitions the portals into
succeeded, alreadyInState (the controller's "Portal state not changed"),
and failed, and is an error only when failed is non-empty. The five
unlock-window tools are described under Scheduled unlock windows and
Scheduled daily unlock windows below.
find_portals is for finding a door when you only know
where it is. Portal names are site codes (01OF05A), and the only
human-readable location text on the controller is each reader's DESCRIPTION.
GetPortals doesn't return it, and neither command takes a filter. So
find_portals reads every page of GetPortals and GetReaders, joins them by
READERKEY, and returns the portals where every term of query appears
(case-insensitive) in the portal name, a reader name, or a reader description.
For example, "maintenance office" matches a reader described as
BREAKROOM TO MAINTENANCE OFFICE. Each match includes its readers' names and
descriptions. The result also lists portalsWithoutDescriptions: portals none
of whose readers has a description, which can only be found by name. It issues
no commands beyond those two.
get_portals itself also accepts RESOLVEDESCRIPTIONS (default true —
on by default, the same opt-out default as every other RESOLVEDESCRIPTIONS
flag in this codebase): unless explicitly set to false, it fills in each
nested reader's own DESCRIPTION field — GetPortals never populates it,
only READERKEY/NAME/PORTALORDER — via one GetReaders full-table fetch
per call (not per portal/reader), using the same src/readerDescriptions.ts
helper as the other RESOLVEDESCRIPTIONS tools. Unlike those tools, which add
a new sibling field (READERDESCRIPTION) to flat records, this fills
DESCRIPTION in directly on each nested reader object, since that's that
reader's own native GetReaders field name. Set RESOLVEDESCRIPTIONS: false
to get readers back exactly as GetPortals returns them, with no
GetReaders call. This makes plain get_portals listings self-describing;
it doesn't replace find_portals, which remains the tool for searching by
name or description rather than just listing.
get_reader_access_history is for finding out who actually badges through a
given reader — useful, for example, when a reader has no DESCRIPTION and
find_portals can't locate it by name. GetAccessHistory has no
READERKEY/PORTALKEY filter, so this tool reads and filters client-side.
Rather than a date range (a real one proved unworkable live — see
specs/archive/get-reader-access-history.md's Goal section), it scans a fixed-size
window of the most recent SCANWINDOW system-wide records (default 2000):
one cheap MAXRECORDS: '1' call discovers the current maximum LOGID, then
the tool walks forward from maxLogid - SCANWINDOW via its own
AFTERLOGID/NEXTLOGID pagination loop (a separate shape from NEXTKEY),
keeping only the records whose READERKEY matches. Each matching record's
PERSONID is enriched with FIRSTNAME/LASTNAME via one GetPerson call
per distinct person (a lookup failure — e.g. for an operator-style
PERSONID — leaves those two fields blank rather than failing the call).
The result is capped at MAXMATCHES (default 100, earliest matches first)
with a truncated flag. get_reader_access_history also accepts
RESOLVEDESCRIPTIONS (default true — on by default, the one
opt-out boolean in this codebase; every other optional boolean flag
defaults to off): unless explicitly set to false, it attaches a single
top-level READERDESCRIPTION field — the human-readable description of the
call's own READERKEY — via one GetReaders full-table fetch. It is
deliberately not duplicated onto each matches entry, since every match
already shares that identical READERKEY by construction. Set
RESOLVEDESCRIPTIONS: false to omit the field entirely (not present at all,
distinguishable from an unknown reader's '') and skip the GetReaders
call.
get_access_history optionally enriches each returned record with the
badge-holder's name via RESOLVENAMES: true (default false): when set, it
calls GetPerson once per distinct PERSONID found in the result (the same
per-request memoization as get_reader_access_history, via the shared
src/personEnrichment.ts helper — no cross-request cache) and adds
FIRSTNAME/LASTNAME/FULLNAME/NOTES to each record, preserving every
original field. This costs one extra GetPerson call per distinct person in
the result, which is why it's opt-in rather than on by default.
get_access_history has no date-range filter: its previous date-range
parameters were removed entirely, closing
#47 — they didn't match
GetAccessHistory's real NBAPI field names, and a live controlled A/B test
this session found that even the correct field names don't work: the
controller silently ignores them and returns the same records regardless of
the requested range, no error, just no effect. Renaming would have only
traded a loud failure for a silently wrong one, so date-range filtering is
dropped rather than fixed — the same reasoning already documented above for
get_reader_access_history.
get_access_history and get_card_access_details both also accept
RESOLVEDESCRIPTIONS (default true — on by default; the same
opt-out default as get_reader_access_history's own RESOLVEDESCRIPTIONS
above, and unlike RESOLVENAMES, which defaults to off): unless explicitly
set to false, each returned record is enriched with the reader's
human-readable READERDESCRIPTION alongside its existing READER (or
PORTALNAME, for get_card_access_details) code, preserving every other
field. Both tools share the same src/readerDescriptions.ts helper
get_reader_access_history uses. It defaults to on rather than off because,
unlike person-name enrichment, the underlying GetReaders fetch has a fixed
cost — this controller's entire reader table (68 readers) fetches in exactly
2 paginated calls regardless of how many result records are returned, so
there's no scaling cost to make callers opt in to. Set
RESOLVEDESCRIPTIONS: false to skip the GetReaders call and get the plain
(unenriched) response. On both get_access_history and
get_card_access_details, RESOLVENAMES and RESOLVEDESCRIPTIONS are
independent flags — either, both, or neither may be requested in the same
call.
get_card_access_details also accepts its own RESOLVENAMES: true (default
false), enriching the response with the card owner's
FIRSTNAME/LASTNAME/FULLNAME/NOTES via the same shared
src/personEnrichment.ts helper get_access_history uses. Unlike
get_access_history (whose response can carry many distinct PERSONIDs, one
per record), GetCardAccessDetails' response carries exactly one PERSONID
at the top level — a card belongs to one person — so this costs a single
GetPerson call per tool call, not one per distinct person. The four
enrichment fields land on the top level of the response, alongside
PERSONID/DISABLED/EXPDATE, rather than being duplicated onto every
ACCESS record.
get_access_level accepts RESOLVEGROUPNAMES (default true — on by
default, the same opt-out default as the other RESOLVE* flags above,
since GetAccessLevel carries exactly one TIMESPECGROUPKEY and one
READERGROUPKEY per call, so resolving both always costs exactly one
fixed-size GetTimeSpecGroups fetch and one fixed-size GetReaderGroups
fetch, never scaling with anything): unless explicitly set to false, it
resolves the response's bare TIMESPECGROUPKEY/READERGROUPKEY foreign
keys into new sibling TIMESPECGROUPNAME/READERGROUPNAME fields, using
the new src/timeSpecGroupNames.ts/src/readerGroupNames.ts helpers.
TIMESPECGROUPKEY is resolved via the full paginated GetTimeSpecGroups
list, filtering client-side for the matching key — never the singular
GetTimeSpecGroup command, which is verified broken on this controller: it
returns CODE=FAIL/ERRMSG="NOT FOUND" even for a genuinely existing group
(the same finding already documented for src/unlockWindow/managed.ts).
READERGROUPKEY is resolved the same way, via the full paginated
GetReaderGroups list, for consistency. An empty/absent key on either axis
independently skips that axis's fetch and yields '' for just that axis's
name, without affecting the other. THREATLEVELGROUPKEY is never
resolved and is left exactly as-is — no NBAPI read command for threat level
groups exists in this server's command surface at all. If the underlying
GetTimeSpecGroups/GetReaderGroups fetch itself fails, that axis's name
resolves to '' and the call still succeeds with the primary
GetAccessLevel data intact — an enrichment failure never loses the primary
data. Set RESOLVEGROUPNAMES: false to skip both fetches and get the
response back exactly as GetAccessLevel provides it.
get_portal_group accepts the same RESOLVEGROUPNAMES flag (default
true, same opt-out default and identical kind of lookup as
get_access_level's own RESOLVEGROUPNAMES above): unless explicitly set to
false, it resolves the response's bare UNLOCKTIMESPECGROUPKEY foreign key
into a new sibling UNLOCKTIMESPECGROUPNAME field, reusing the same
src/timeSpecGroupNames.ts helper (and so the same full-paginated-list
resolution, never the broken singular GetTimeSpecGroup command). A single
GetPortalGroup response carries exactly one UNLOCKTIMESPECGROUPKEY, so
this always costs exactly one fixed-size GetTimeSpecGroups fetch, never
scaling with anything. The already-human-readable PORTALS sub-list
({PORTALKEY, NAME} per portal) is left completely unchanged.
THREATLEVELGROUPKEY is never resolved — no NBAPI read command for
threat level groups exists in this server's command surface. An
empty/absent UNLOCKTIMESPECGROUPKEY skips the fetch entirely and yields
'' for the name; if the underlying GetTimeSpecGroups fetch itself fails,
UNLOCKTIMESPECGROUPNAME resolves to '' and the call still succeeds with
the primary GetPortalGroup data (including PORTALS) intact. Set
RESOLVEGROUPNAMES: false to skip the fetch and get the response back
exactly as GetPortalGroup provides it.
get_portal_groups accepts the same RESOLVEGROUPNAMES flag (default
true, same opt-out default and field name as the singular
get_portal_group above — this is its explicitly-planned follow-on):
unless explicitly set to false, it resolves every returned group's bare
UNLOCKTIMESPECGROUPKEY foreign key into a new sibling
UNLOCKTIMESPECGROUPNAME field, reusing the same
src/timeSpecGroupNames.ts helper. Unlike the singular tool (whose response
carries exactly one UNLOCKTIMESPECGROUPKEY, so it does at most one
conditional fetch), this plural tool builds the
fetchTimeSpecGroupNames map once per call — only if at least one group
on the page carries a non-empty UNLOCKTIMESPECGROUPKEY (zero
GetTimeSpecGroups calls if every group's key on the page is empty) — then
looks every group up against that same shared map, the same one-fetch-per-
page cost shape as get_time_spec_groups's own RESOLVEMEMBERNAMES above,
never one fetch per group. Unlike GetPortalGroup (singular), GetPortalGroups'
response is already flat per item — DETAILS.PORTALGROUPS.PORTALGROUP[], no
per-item PORTALGROUP wrapper — so no per-item unwrap is applied; that
wrapper quirk belongs only to the singular command's own response envelope.
The already-human-readable PORTALS sub-list ({PORTALKEY, NAME} per
portal) is left completely unchanged on every group.
THREATLEVELGROUPKEY is never resolved — no NBAPI read command for
threat level groups exists in this server's command surface. A group with an
empty/absent UNLOCKTIMESPECGROUPKEY gets UNLOCKTIMESPECGROUPNAME: ''
without needing a match; a group whose key has no match in the fetched map
also gets ''. If the underlying GetTimeSpecGroups fetch itself fails,
every group's UNLOCKTIMESPECGROUPNAME resolves to '' and the call still
succeeds with every group's other fields (including PORTALS) intact — an
enrichment failure never loses the primary data. Set RESOLVEGROUPNAMES: false to skip the fetch and get groups back exactly as GetPortalGroups
provides them.
list_events accepts RESOLVEPARTITIONNAMES (default true — on by
default, the same opt-out default as the other RESOLVE* flags above):
unless explicitly set to false, each returned event's bare PARTITIONID
is resolved into a new sibling PARTITIONNAME field via one GetPartitions
fetch per call — not per event, since GetPartitions takes no
STARTFROMKEY at all and always answers every partition in a single
response, so the cost never scales with how many events come back. This is
backed by the new src/partitionNames.ts helper, mirroring
src/readerDescriptions.ts's shape exactly (a Map-returning fetch
function that never throws). An event whose PARTITIONID has no match in
the fetched map resolves to PARTITIONNAME: '', and if the underlying
GetPartitions fetch itself fails, every event's PARTITIONNAME resolves
to '' and the call still succeeds with every other field (including
ACTIONS) intact — an enrichment failure never loses the primary data. Set
RESOLVEPARTITIONNAMES: false to skip the GetPartitions call and get the
response back exactly as ListEvents provides it.
get_time_spec_groups accepts RESOLVEMEMBERNAMES (default true — on
by default, the same opt-out default as the other RESOLVE* flags above,
since resolving every group's members on a page always costs exactly one
fixed-size GetTimeSpecs fetch, never scaling with how many groups/members
are on the page): unless explicitly set to false, each group's
TIMESPECKEYS.TIMESPECKEY field — which GetTimeSpecGroups returns as bare
TIMESPECKEY string(s) — is replaced with a list of {TIMESPECKEY, NAME}
objects, matching this codebase's own convention for other group-membership
sub-lists that NBAPI already returns as objects natively (get_access_level_group's
ACCESSLEVELS, get_reader_group's READERS). A member key with no match
in the fetched GetTimeSpecs table (an unknown/deleted time spec) resolves
to NAME: '' rather than being omitted. Every other field
(TIMESPECGROUPKEY, the group's own NAME, DESCRIPTION) is unchanged. The
name lookup uses the new src/timeSpecNames.ts helper — one full paginated
GetTimeSpecs fetch per call, regardless of how many groups/members are on
the page — and reuses keyList (src/paging.ts, relocated from
src/unlockWindow/managed.ts) to normalize the bare-key collection. If the
underlying GetTimeSpecs fetch itself fails, every member's NAME resolves
to '' and the call still succeeds with every group's own fields intact —
an enrichment failure never loses the primary data. RESOLVEMEMBERNAMES
applies only to this plural tool, not the singular get_time_spec_group,
which is verified broken (CODE=FAIL/ERRMSG="NOT FOUND") on this
controller even for a genuinely existing group, independent of this change.
Set RESOLVEMEMBERNAMES: false to skip the fetch and get TIMESPECKEYS
back exactly as GetTimeSpecGroups provides it (bare string or array of
strings).
Write tools and Destructive tools
write (needs only NETBOX_ENABLE_WRITES) and destructive (needs
NETBOX_ENABLE_WRITES and NETBOX_ENABLE_DESTRUCTIVE). Every write
tool's input schema declares exactly the documented PARAMS fields for its
command, matching required/optional as documented — see the "Write access"
section above for the gating rules and the shared SUCCESS/WRITE:/
DESTRUCTIVE: conventions.
Tool | Wraps NBAPI command | Required params | Tier |
|
|
| write |
|
|
| write |
|
|
| write |
|
|
| write |
|
|
| write |
|
|
| write |
|
|
| write |
|
|
| write |
|
|
| write |
|
|
| write |
|
|
| destructive |
|
|
| destructive |
|
|
| write |
|
|
| write |
|
|
| destructive |
|
|
| write |
|
|
| write |
|
|
| destructive |
|
|
| write |
|
|
| write |
|
|
| destructive |
|
|
| write |
|
|
| write |
|
|
| destructive |
|
|
| write |
|
|
| write |
|
|
| destructive |
|
|
| write |
|
|
| write |
|
|
| destructive |
|
|
| write |
|
|
| write |
|
|
| destructive |
|
|
| write |
|
|
| write |
|
|
| write |
|
|
| destructive |
|
|
| write |
|
|
| write |
|
|
| destructive |
|
|
| write |
|
|
| write |
|
|
| write |
|
|
| write |
|
|
| write |
|
|
| write |
|
|
| write |
|
| — | write |
|
|
| write |
|
| — | write |
|
|
| write |
|
|
| write |
|
|
| destructive |
|
|
| write |
|
|
| write |
|
|
| destructive |
|
|
| write |
|
|
| write |
|
|
| destructive |
|
|
| write |
|
|
| write |
|
|
| destructive |
The twelve hardware tools (*_mercury_panel, *_network_node, *_sio —
six writes and three destructive deletes, plus the six reads in the table
above) are not live-verified: the reference controller this project is
developed against has no Mercury panels and no SIOs, so their read tools
SKIP-pass in npm run test:live and their write tools have never been issued
against real hardware. They are built to the April-2025 NBAPI v2 guide alone
— see the header comment in src/tools/hardware.ts and
docs/reference/nbapi-command-diff.md.
modify_portal_group and modify_reader_group always replace the group's
membership with the PORTALKEYS/READERKEYS you send — on this controller
(6.2.0, verified live) an omitted or unparsed list empties the group instead
of leaving it unchanged, so both tools require the complete membership.
trigger_event is unverified live on 6.x; NETBOX_EVENT_API_PATH is
available to override the request path if your controller serves the Event
API separately from the main NBAPI path (see the environment variable table
above).
switch_partition changes the partition for every later call made by this
server process, not just the caller's own next request — the NBAPI session
is cached and reused, and SwitchPartition has no per-call scope.
Person / credential tools and Active Directory
If this NetBox instance syncs person/access-level data from Active
Directory, any write this server makes to a synced field is silently
overwritten on the next AD sync — add_person and modify_person both
carry this caution in their tool descriptions. Separately, modify_person's
ACCESSLEVELS has two syntaxes: a plain list of access-level name strings
replaces the person's entire set of access levels, while a list of
{ ACCESSLEVELNAME, DELETE?, ACTDATE?, EXPDATE?, AUTOREMOVE? } blocks is
additive (adds/removes individual levels without touching the rest). Mixing
the two syntaxes in one call is rejected client-side before any command is
sent.
Scheduled unlock windows
"Unlock these doors from start to end" is one call —
schedule_unlock_window — and the controller itself enforces the
schedule: no process has to stay alive to relock the doors, so the MCP host
can go away the moment the call returns.
How it works (the same objects an operator creates by hand). The window is
realised as a Holiday covering the dates, a Time Spec with no weekdays
and only one holiday group ticked, and a Portal Group whose Unlock Time
Spec is that time spec's group. A time spec with no weekdays and holiday
group G ticked is active only on dates covered by a holiday in group G, so
the portals unlock exactly on the window's dates and clock range. A window
that spans midnight is split into up to three segments — first (start time →
23:59 on the start date), middle (00:00 → 23:59 on every date strictly
between, if any) and last (00:00 → end time on the end date) — each with its
own holiday + time spec pair.
Managed objects and the single-window model. Everything the tool creates
is named with NETBOX_UNLOCK_NAME_PREFIX (default MCP Unlock Window): the
portal group is named exactly <prefix>, the time spec group <prefix> time specs (never <prefix> — group names are unique across group types on this
controller, so a portal group and a time spec group cannot share a name), and
the per-segment holidays and time specs <prefix> first, <prefix> middle,
<prefix> last. Names are the identity. There is one managed window at a
time: scheduling a new one rewrites those same objects (modifying what
exists, adding what is missing, deleting leftover segments from the previous
window), and calling it twice with the same arguments is idempotent (only
Modify/Get commands, same keys). The composite tools never modify or delete
any object whose name is not exactly one of those; a user-created object
that happens to carry one of those names is treated as managed. The apply
order is fixed — resolve portals, managed time spec group, per-segment
holiday + time spec, group membership, delete leftovers, managed portal group
— and every step is read back and compared to the plan before the tool
reports verified: true; any mismatch is a tool error describing the field.
If any apply step fails, the tool rolls back by deleting every managed
holiday and time spec written so far (mirroring cancel_unlock_window's
cleanup) before returning the error, so no partial window is left active;
the error text names the failed step, the controller's message, and what the
rollback removed.
Reserved holiday groups. NetBox has exactly eight holiday groups (1–8),
shared by every time spec on the controller. NETBOX_UNLOCK_HOLIDAY_GROUPS
(default 8,7,6) reserves one group per segment kind (first, middle,
last, in that order). Reserve groups nothing else on the controller uses.
With fewer than three groups configured, only windows needing that many
segments can be scheduled (one group = same-day windows only); the tool never
doubles up a group, because two segments sharing one would each unlock on the
other's dates.
The side-effect check and acknowledgeSideEffects. A holiday in group
G suppresses, on its dates, every time spec that does not tick G — an
access level whose time spec ticks only groups 1–3, say, would lose access
during a window that uses group 8. Before writing anything,
schedule_unlock_window reads every time spec and holiday and reports
suppressedTimeSpecs (time specs other than Never and its own that lack a
group the plan uses) and overlappingHolidays (non-managed holidays whose
dates intersect the window — reported, never touched). If any time spec would
be suppressed, the call is refused with nothing written unless
acknowledgeSideEffects=true. dryRun=true returns the plan and the report
without writing anything, whether or not you acknowledged. The built-in
Always time spec ticks all eight groups and is never affected.
Cancelling. cancel_unlock_window first, if the managed portal group
exists, points it at the built-in Never time spec group (re-sending its
current portals); then, regardless of whether that portal group exists,
deletes the managed holidays and empties the managed time spec group and
deletes the managed time specs. The last two are best-effort: if the
controller refuses them, the tool still succeeds and lists what was left
under leftBehind, because once the portal group (if any) is on Never and
no managed holiday exists, nothing can unlock. The managed portal group and
time spec group are kept (pointing at Never / empty) and reused by the next
window. The tool reports there was nothing to cancel only when no managed
object of any kind — portal group, time spec group, holiday, or time spec —
exists.
get_unlock_window (always registered, read-only) shows the current managed
state — the portal group and whether it points at the managed time spec
group, that group's members (read from GetTimeSpecGroups, because
GetTimeSpecGroup returns FAIL/NOT FOUND on the verified 6.2.0
controller), the managed time specs and holidays — plus the window derived
from them and activeNow on the host clock.
Limits and caveats.
A window must end in the future and be at most 31 days long. Holidays are capped at 30 per partition, so a window whose segments would push past that is refused.
portalKeysare keys only (useget_portalsorfind_portalsto map names); an unknown key is refused before anything is written.End of day on the NBAPI is
23:59(the built-inAlwaysuses it), andENDTIMEis inclusive through the end of that minute, so there is no midnight gap between segments of a multi-day window. A window's door relocks up to 59 seconds after the statedendminute (observed live: a window ending08:27relocked at08:27:59controller time). Anendof00:00means "up to 23:59 of the previous day".Times are the controller's local time. The MCP host is assumed to share the controller's timezone; the host clock is used only to reject windows that have already elapsed and to compute
activeNow.The physical unlock is not observable through the NBAPI: no read command exposes portal state, and
GetEventHistorycarries no Unlock/Relock activity. The tool verifies its work by reading the configuration objects back and comparing them to the plan; confirm the door itself on Monitor → Portal Status or in person.set_portals_stateis the immediate alternative: itsUNLOCKis an Extended Unlock that lasts untilLOCK, with nothing scheduling the relock.
Session handling, retry-on-expired-session, and error mapping are all automatic and match the NBAPI documentation:
The first tool call triggers
Login; the session ID is cached and reused for every later call in the same server run.If a call fails with
APIERROR 5(auth failure / expired session), the client transparently re-logs-in once and retries the original command.An
<APIERROR>response surfaces as a tool error like"5: There was an authentication failure."A
<CODE>FAIL</CODE>response surfaces as a tool error including the controller'sERRMSGtext verbatim.A
<CODE>NOT FOUND</CODE>response (e.g. an unknownPERSONID) is returned as a normal, non-error result stating "not found" — it is not thrown as an exception.SIGINT/SIGTERMtriggerLogoutfor any active session before the process exits.
Scheduled daily unlock windows
The companion to Scheduled unlock windows above: "unlock these doors from
dailyStartTime to dailyEndTime, every day from startDate through
endDate" — a single partial-day window that recurs daily across a date
range, which schedule_unlock_window cannot express (it models one
continuous span, so a multi-day request there keeps doors unlocked overnight
on the days strictly between the first and last). schedule_daily_unlock_window
relocks the doors every night outside the daily window.
How it works (reusing the same mechanism). This reuses the exact
holiday + time spec + portal group mechanism described above: a Holiday
spanning the whole date range, a Time Spec with no weekdays and
only the one reserved daily holiday group ticked, and a Portal Group whose
Unlock Time Spec is that time spec's group. Because a time spec with no
weekdays and holiday group G ticked is active during its
STARTTIME-ENDTIME on every date covered by a holiday in group G,
one holiday (covering every date in the range) paired with one partial-day
time spec already expresses "the same time-of-day window, every day in the
range" — no first/middle/last segment-splitting is ever needed, unlike
the continuous feature, whose planner has to split a multi-day span into up
to three segments precisely because a middle day needs a full 00:00-23:59
grant. This feature's plan is always exactly one segment.
Environment variables and collision safety. NETBOX_DAILY_UNLOCK_HOLIDAY_GROUP
(default 5) is the single holiday group this feature reserves, and
NETBOX_DAILY_UNLOCK_NAME_PREFIX (default MCP Daily Unlock Window) names
its managed objects — see the environment variable table above for both.
NETBOX_DAILY_UNLOCK_HOLIDAY_GROUP is validated at startup to never be a
member of NETBOX_UNLOCK_HOLIDAY_GROUPS, so this feature and the
continuous one always use disjoint holiday groups, and — because each
feature's managed portal group and time spec group are named after its own
prefix — disjoint managed-object names as well. Consequently the two
features can be active at the same time: a daily window and a continuous
window may both be scheduled and unlocking doors concurrently, with no
shared NBAPI object between them. This also means schedule_daily_unlock_window's
side-effect check does not special-case a currently-active continuous
window — if its dates happen to overlap the daily plan's dates, it is
reported like any other foreign time spec/holiday, which is the correct,
general behaviour rather than a special case.
Managed objects and the single-daily-window model. Everything this
tool creates is named with NETBOX_DAILY_UNLOCK_NAME_PREFIX: the portal
group is named exactly <prefix>, the time spec group <prefix> time specs
(never <prefix> — group names are unique across group types on this
controller, same as the continuous feature), and the one holiday and one
time spec <prefix> schedule (same name, different object tables — no
collision). Names are the identity. There is one managed daily window at a
time: scheduling a new one rewrites those same objects (modifying what
exists, adding what is missing), and calling it twice with the same
arguments is idempotent (only Modify/Get commands, same keys). The tools
never modify or delete any object whose name is not exactly one of those; a
user-created object that happens to carry one of those names is treated as
managed. The apply order is fixed — resolve portals, managed time spec
group, managed holiday, managed time spec, group membership, managed portal
group — and every step is read back and compared to the plan before the
tool reports verified: true; any mismatch is a tool error describing the
field. If any apply step fails, the tool rolls back by deleting the managed
holiday and time spec written so far (mirroring cancel_daily_unlock_window's
cleanup) before returning the error, so no partial window is left active;
the error text names the failed step, the controller's message, and what
the rollback removed.
The side-effect check and acknowledgeSideEffects. Exactly as for the
continuous feature: a holiday in the reserved daily group suppresses, on its
dates, every time spec that does not tick that group. Before writing
anything, schedule_daily_unlock_window reads every time spec and holiday
and reports suppressedTimeSpecs (time specs other than Never and its own
that lack the reserved daily group) and overlappingHolidays (non-managed
holidays whose dates intersect the window — reported, never touched). If any
time spec would be suppressed, the call is refused with nothing written
unless acknowledgeSideEffects=true. dryRun=true returns the plan and the
report without writing anything, whether or not you acknowledged.
Cancelling. cancel_daily_unlock_window first, if the managed
portal group exists, points it at the built-in Never time spec group
(re-sending its current portals); then, regardless of whether that portal
group exists, deletes the managed holiday if it exists, and empties the
managed time spec group and deletes the managed time spec if either exists.
The last two are best-effort: if the controller refuses them, the tool still
succeeds and lists what was left under leftBehind, because once the portal
group (if any) is on Never and no managed holiday exists, nothing can
unlock. The managed portal group and time spec group are kept (pointing at
Never / empty) and reused by the next daily window. The tool reports there
was nothing to cancel only when no managed object of any kind — portal
group, time spec group, holiday, or time spec — exists.
get_daily_unlock_window (always registered, read-only) shows the current
managed state — the portal group and whether it points at the managed time
spec group, that group's members, the one managed time spec and holiday
(singular, not arrays — this feature never has more than one of each) —
plus the window derived from them and activeNow on the host clock.
Limits and caveats.
A window must end in the future and span at most 31 days (
endDate - startDate). Holidays are capped at 30 per partition, so a window whose one holiday would push past that is refused.portalKeysare keys only (useget_portals/find_portalsto map names); an unknown key is refused before anything is written.An overnight-crossing daily window is not supported:
dailyEndTimemust be strictly later thandailyStartTime(same-day time-of-day only). A request like "10 PM to 5 AM, every night" is rejected — a future extension could express this as two segments, but it is out of scope here.There is no per-weekday selectivity: the whole
[startDate, endDate]range unlocks every day at the given time-of-day (no "weekdays only" filtering).Dates are
YYYY-MM-DDand times areHH:MM, both controller-local; the same midnight-relock and host/controller-clock-assumption caveats as the continuous feature above apply here too.The physical unlock is not observable through the NBAPI — confirm the door on Monitor → Portal Status or in person, exactly as above.
Testing
npm testRuns the full unit test suite against a hand-rolled, in-memory HTTP stub — no network access and no live controller are required or contacted.
Live smoke test (optional)
npm run test:liveThis calls all read tools except get_unlock_window/get_daily_unlock_window
(35 of the 37 — see Tools exposed below) against a real, configured controller and
prints a PASS/FAIL line per tool plus a summary, exiting non-zero if
anything failed. It only runs if NETBOX_BASE_URL, NETBOX_USERNAME, and
NETBOX_PASSWORD are all set (loaded from .env if present); otherwise it
prints one line saying live testing was skipped and exits 0. It never prints
the value of NETBOX_PASSWORD, under any circumstance, and it never issues
a write/control command regardless of NETBOX_ENABLE_WRITES. npm test
never runs this script and never requires .env to exist.
Live write smoke test (optional, opt-in twice)
npm run test:live:write # CRUD round-trips only
npm run test:live:write -- --go # ... plus the real 2-minute unlock window
npm run test:live:write -- --go --start 14:30 # pin the unlock time (1-60 min ahead)PowerShell: on at least one PowerShell/npm combination this silently drops flags passed after
--(npm printsnpm warn Unknown cli config "--go"and the flag never reaches the script — observed live, 2026-09-15). If--godoesn't trigger phase (c), call the script directly instead:npx tsx scripts/live-check-write.ts --go.
This skips with one line and exit 0 — making no network call — unless
NETBOX_BASE_URL, NETBOX_USERNAME, NETBOX_PASSWORD,
NETBOX_ENABLE_WRITES=true and NETBOX_LIVE_TEST_PORTALKEY are all set.
Most of the round-trips below issue deletes/removes directly against the
controller (independent of the MCP server's own NETBOX_ENABLE_DESTRUCTIVE
gating, which this script bypasses by calling the NBAPI client directly), so
set NETBOX_ENABLE_DESTRUCTIVE=true before running it.
Otherwise it round-trips add → get → modify → get → delete for a time spec, a
time spec group, a holiday, a reader group, and a portal group under the
distinct prefix MCP livecheck (the portal group's unlock time spec group is
Never and the holiday is in 2099, so nothing can unlock), asserting each
read-back. It then round-trips a person (AddPerson → GetPerson →
ModifyPerson → GetPerson) plus a credential on that person (AddCredential
→ GetPerson with WANTCREDENTIALID → ModifyCredential with DISABLED=1
→ read-back → RemoveCredential → read-back) → RemovePerson, accepting
either NOT FOUND or DELETED=TRUE on the final GetPerson (never sends
PERSONPURGE); an access level (AddAccessLevel with TIMESPECGROUPKEY
Never → GetAccessLevel → ModifyAccessLevel → read-back →
DeleteAccessLevel → read-back gone) plus an access level group built from a
second temporary access level (AddAccessLevelGroup → GetAccessLevelGroup
→ ModifyAccessLevelGroup → read-back → DeleteAccessLevelGroup, tolerating
the same FAIL/ERRMSG="NOT FOUND" quirk documented for GetTimeSpecGroup
against an empty collection); a threat level plus a threat level group
(AddThreatLevel → AddThreatLevelGroup → ModifyThreatLevel →
ModifyThreatLevelGroup → RemoveThreatLevelGroup → RemoveThreatLevel,
proven gone by a second RemoveThreatLevel failing — GetThreatLevels is
not used for round-trip verification here, and SetThreatLevel is never
called); InsertActivity
with a timestamped USERACTIVITY record; a UDF list item round-trip via
ModifyUDFListItems (or a recorded SKIPPED pass if no UDF list is
configured); and GetPartitions → SwitchPartition back to the session's
own partition (AddPartition is never called). It cleans up any
MCP livecheck leftovers — including persons, access levels/groups, and
threat levels/groups — from an aborted run, both before and after the round
trips.
It then estimates the controller's clock from the newest GetAccessHistory
record and refuses to run the door phase — regardless of --go — when that
estimate disagrees with the host clock by more than 2 minutes; window times
passed to schedule_unlock_window are always controller-local, not host-local.
With --go — pass it only after notifying the user (push notification
plus a chat message giving the exact unlock and relock clock times) and
receiving a go-ahead, because they observe the door — it prints a
HEADS-UP line, schedules a real 2-minute unlock of the designated portal
through the real schedule_unlock_window executor (unlock at now + 2 min and
relock at now + 4 min, or at --start HH:MM), prints OBSERVE: portal ... should unlock at HH:MM and relock at HH:MM — confirm on Monitor → Portal Status, polls get_unlock_window every 30 s until one minute after relock,
then calls cancel_unlock_window and asserts the managed portal group is on
Never with no managed holiday, time spec, or time spec group member left
(leftBehind is tolerated but reported). It refuses that phase if a managed
window already exists (so it never replaces a real one); apart from the
supervised single actions below, it never touches outputs, TriggerEvent, or
portal lock/unlock actions, never prints the password, exits non-zero on any
failed assertion (still cancelling the window first), and npm test never
runs it.
Supervised single actions
npm run test:live:write -- --action unlock_portal
npm run test:live:write -- --action set_threat_level --value High--action <name> [--value <v>] runs exactly one write against the
designated portal (or its strike output) instead of the full flow above —
skipping phases (b), (b2), and (c) entirely. It still requires
NETBOX_ENABLE_WRITES=true and the credential variables (same skip line as
above), but not NETBOX_ENABLE_DESTRUCTIVE, since no deletes happen. It
refuses to run — exit 2, no network call — if --action is combined with
--go, if the action name is unknown, or if set_threat_level's required
--value is missing. It prints the exact command and params sent (never
credentials), the controller's CODE/DETAILS or ERRMSG, and an
OBSERVE: ... line describing what to check at the door or on Monitor; a
FAIL with ERRMSG "Portal state not changed" is reported as
PASS-with-note rather than a failure. Exits 0 on success or already-in-state,
1 otherwise, and unknown/invalid arguments exit 2.
Every action is reversible:
Action | Effect | Reverse |
|
|
|
|
| — |
|
| — |
|
|
|
|
|
|
|
| — |
| the real |
|
|
| — |
|
| — |
|
|
|
|
|
|
|
| — |
activate_output/deactivate_output resolve the strike output by finding
the GetOutputs entry whose NAME starts with the designated portal's
NAME (e.g. portal "02OF01A" → output "02OF01A EL"), failing clearly if
none is found. AddPartition is never reachable through --action, same as
the rest of this script.
trigger_event_activate/trigger_event_deactivate require --value <EVENTNAME> — the name of a NetBox event that must already exist (events
cannot be created via the NBAPI; create it first in the NetBox UI). This is
the only live verification path for trigger_event — the full CRUD
flow above never calls TriggerEvent. Both actions go through the same
NetboxClient.call as every other command, so NETBOX_EVENT_API_PATH
routing still applies; the script prints which URL path it used.
Live write smoke test — daily unlock window (optional, opt-in twice)
npm run test:live:write:daily # CRUD round-trips only
npm run test:live:write:daily -- --go # ... plus the real 2-minute daily unlock windowPowerShell: see the same-named caveat under "Live write smoke test" above — if
--gois silently dropped, usenpx tsx scripts/live-check-write-daily.ts --goinstead (verified live, 2026-09-15, on portal02OF01A: unlock/relock confirmed in person, 16/16 steps PASS).
A sibling script to npm run test:live:write above, covering
schedule_daily_unlock_window/cancel_daily_unlock_window/
get_daily_unlock_window (kept as a separate npm script rather than
chained onto test:live:write so -- --go keeps reaching the script it is
meant for). It skips with one line and exit 0 — making no network call —
under the same gating as npm run test:live:write. Otherwise it round-trips
add → get → modify → get → delete for a time spec, a time spec group, a
holiday, and a portal group under the distinct prefix MCP livecheck daily
(the portal group's unlock time spec group is Never and the holiday is in
2099, so nothing can unlock), asserting each read-back, then runs the same
controller-clock-skew guard as test:live:write (refusing the door phase
above a 2-minute skew regardless of --go).
With --go — pass it only after notifying the user and receiving a
go-ahead, same as above — it prints a HEADS-UP line, schedules a real
2-minute daily unlock covering only today's date (dailyStartTime = now
2 min,
dailyEndTime= now + 4 min) on the designated portal through the realschedule_daily_unlock_windowexecutor, prints anOBSERVE: ...line, pollsget_daily_unlock_windowevery 30 s until one minute after relock, then callscancel_daily_unlock_windowand asserts the managed portal group is onNeverwith no managed holiday or time spec left (leftBehindis tolerated but reported). It refuses that phase if a managed daily window already exists, never touches persons, credentials, access levels, threat levels, outputs, events, partitions, or UDF lists, never prints the password, exits non-zero on any failed assertion (still cancelling the window first), andnpm testnever runs it.
Out of scope
Photo upload — the multipart POST to
/nbws/goforms/upload, which is not an NBAPI XML command at all. Reading a person's photo is supported:get_picturewrapsGetPictureand returns the Base64 JPEG unmodified.Elevator and floor writes — not a choice: neither the v1 nor the v2 guide documents any
Add/Modify/Deletecommand for elevators or floors, soget_elevators/get_floorsare read-only because the API is. See the diff report.Data Operations (bulk person import/export) — the LenelS2 Data Operations guide describes a web-UI and NAS-polling feature with no API of its own, so there is nothing to wrap. No import-file builder, export parser or NAS automation is planned.
Alarm-queue workflow commands (
AckAlarm,AckEvent,AlarmClearActions,AlarmSetOwner,EventClearActions) —get_alarmsreads alarms, but this server does not drive an operator alarm queue.StreamEvents/ the persistent/appdevent/nbapi/eventpush feedMAC-based authentication (session-login only)
The S2 Global API variant
The deprecated NBAPI commands (
EditPerson,EditThreatLevel,EditThreatLevelGroup,GetAccessDataLog,GetAccessCardDetails,LoginUserName,LoginUserPassword)Multiple concurrent managed unlock windows, per-window naming, or any persistence on the MCP host (there is one managed window; names are its identity)
Resolving portals by name in the composite tools (keys only —
get_portals/find_portalsmap names)Automatically deleting the managed portal group or time spec group on cancel (they stay, pointing at
Never/ empty, and are reused)Any scheduler on the host (Task Scheduler, in-process timers) — the controller is the only scheduler
Confirmation prompts inside the server — the MCP host's permission model and the environment gates are the controls
Any GUI/dashboard beyond the MCP tool surface
See specs/archive/s2-netbox-mcp-write.md for the full requirements the write-tool
surface was built against, and specs/archive/s2-netbox-mcp.md for the
original read-only v1 spec (archived — all its acceptance criteria passed,
including live verification).
Contributing
Bug reports, feature requests, and PRs are welcome — see CONTRIBUTING.md for the
workflow (issue first, branch naming, PR conventions) and the physical-safety note that
applies to any change touching write/destructive tools.
Security
Found a vulnerability? See SECURITY.md for how to report it privately and what the
physical-safety blast radius looks like at each configuration level.
Available Tools
51 toolscheck_connectionARead-only
Confirms the server can authenticate to the configured S2 NetBox controller and returns the NBAPI version string (wraps GetAPIVersion). No parameters required.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With readOnlyHint=true already declaring this a safe read, the description adds genuine behavioral context: it performs an authentication handshake against the configured controller and returns the API version string (wrapping GetAPIVersion). It does not describe failure behavior (error vs. false) or timeout/retry traits, which would be valuable for a connection check.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence delivers the purpose, the return value, the underlying implementation, and the parameter situation with zero filler. Nothing could be removed without losing information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple, no-parameter, read-only diagnostic, the description covers what it does and what it returns, and there is no output schema requiring further explanation. The only real gap is failure/error behavior, which matters somewhat for a connectivity check but does not block correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are zero parameters and schema coverage is 100%, so the baseline is 4. The explicit note that no parameters are required is accurate and consistent with the empty schema, adding nothing that contradicts it.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: it confirms server-to-controller authentication and returns the NBAPI version string, naming the underlying GetAPIVersion call. It is unambiguous, but it does not differentiate itself from the sibling diagnostic tools (e.g. ping_app), so an agent must still infer which connectivity check to pick.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied rather than stated: the description says it confirms authentication, so an agent can infer it is a connectivity/credential check. There is no explicit when-to-use/when-not guidance and no mention of the alternative ping_app sibling, leaving routing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
find_portalsARead-only
Finds portals (doors) by location or name. Portal names are site codes (e.g. 01OF05A), so this also searches each portal's reader names and reader descriptions (e.g. 'WORKSHOP TO MAINTENANCE OFFICE'). Case-insensitive; a portal matches when every whitespace-separated term appears in its name, a reader name, or a reader description. Reads every page of GetPortals and GetReaders and joins them by READERKEY. The result also lists portals with no reader description, which can only match by name.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Required. Search terms, e.g. "maintenance office", "electrical closet", or "01OF05". |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond readOnlyHint=true, the description discloses substantial behavior: case-insensitivity, term-matching rules (every whitespace-separated term must appear), pagination ('reads every page'), the READERKEY join between GetPortals and GetReaders, and the edge case of portals with no reader description. This is rich context an agent cannot get from annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded in the first sentence, followed by matching semantics and implementation detail. Four sentences with little waste, though the GetPortals/GetReaders join detail is slightly deeper than most agents need for invocation.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter read tool with no output schema, the description covers matching rules, edge cases, and data sources well. It stops short of describing the shape of returned portal records, but the matching and scoping semantics are complete enough to invoke correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 1 parameter and 100% schema coverage, baseline is 3. The description adds real meaning about how the query string is interpreted (case-insensitive, multiple whitespace-separated terms, matched against three fields), which goes beyond the schema's example-only description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Finds') and resource ('portals (doors)') and immediately clarifies that names are site codes and that the search spans reader names/descriptions. This search behavior implicitly differentiates it from the list-all sibling get_portals without needing to name it.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains the search mechanism (matching name/reader name/reader description) and mentions it reads GetPortals and GetReaders, implying use when you need a filtered lookup rather than a full dump. However, it never explicitly states when to use this over get_portals or names the alternative, leaving the routing decision to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_access_historyARead-only
Returns historical access (grant/deny) records for optional filters (wraps NBAPI GetAccessHistory). Identifies a person by ENCODEDNUM/HOTSTAMP, not PERSONID — GetAccessHistory has no PERSONID parameter. Set RESOLVENAMES: true to enrich each returned record with the badge-holder's FIRSTNAME/LASTNAME/FULLNAME/NOTES (default false — off); enabling it costs one extra GetPerson call per distinct person found in the result, which is why it is opt-in rather than on by default. RESOLVEDESCRIPTIONS defaults to true — the only default-on optional boolean in this codebase (an inverted, opt-out default, unlike RESOLVENAMES/dryRun-style flags elsewhere): each returned record is enriched with the reader's human-readable READERDESCRIPTION via one GetReaders full-table fetch per call (not per record, since the reader table is small and fixed-size); set RESOLVEDESCRIPTIONS: false to skip it.
| Name | Required | Description | Default |
|---|---|---|---|
| ORDER | No | Optional. Sort order for returned records. | |
| HOTSTAMP | No | Optional. Restrict results to this hot-stamp number. | |
| AFTERLOGID | No | Optional. Return records strictly after this LOGID. | |
| CARDFORMAT | No | Optional. Card format of ENCODEDNUM/HOTSTAMP. | |
| ENCODEDNUM | No | Optional. Restrict results to this encoded card number. | |
| MAXRECORDS | No | Optional. Maximum number of records to return. | |
| STARTLOGID | No | Optional. Begin returning records at this LOGID. | |
| RESOLVENAMES | No | Optional (default false). Enrich each returned record with the badge-holder's FIRSTNAME/LASTNAME/FULLNAME/NOTES via one extra GetPerson call per distinct person found in the result. | |
| RESOLVEDESCRIPTIONS | No | Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Enrich each returned record with the reader's human-readable READERDESCRIPTION via one GetReaders full-table fetch per call (not per record). Set to false to skip it. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint annotation, it discloses the exact cost model of each enrichment flag (one GetPerson per distinct person; one GetReaders full-table fetch per call, not per record), flags a codebase-wide anomaly (RESOLVEDESCRIPTIONS is the only default-on optional boolean, an inverted opt-out), and states the identity-key constraint. This is materially more than the annotations provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded well, but the third sentence is a single bloated construct with nested parentheticals, and it largely restates the RESOLVENAMES/RESOLVEDESCRIPTIONS schema descriptions rather than only adding the rationale. Useful content, but it does not read as tight.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 9-parameter read-only query tool with no output schema and full schema coverage, the description covers purpose, identity semantics, per-flag cost, and the unusual default behavior. An agent has everything needed to call it correctly and to decide whether to enable enrichment.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real semantic value the schema lacks: the warning that a person is identified by ENCODEDNUM/HOTSTAMP and not PERSONID (GetAccessHistory has no PERSONID parameter), plus the rationale behind the RESOLVE flag defaults. ORDER/AFTERLOGID/STARTLOGID/MAXRECORDS are left to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening clause gives a specific verb and resource ('Returns historical access (grant/deny) records') and even names the underlying NBAPI operation, so the agent knows exactly what data comes back. It does not explicitly differentiate itself from near-neighbors like get_event_history or get_reader_access_history, which keeps it short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear guidance for the two optional enrichment flags (when to turn RESOLVENAMES on, and that RESOLVEDESCRIPTIONS is opt-out by default), which is genuinely useful. However there is no guidance on when to prefer this tool over sibling history tools, so usage context is only implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_access_levelARead-only
Returns the details of a single access level for a given ACCESSLEVELKEY (wraps NBAPI GetAccessLevel). RESOLVEGROUPNAMES defaults to true — an inverted, opt-out default (unlike most optional booleans in this codebase): the raw response carries only bare TIMESPECGROUPKEY/READERGROUPKEY/THREATLEVELGROUPKEY foreign keys, so this resolves TIMESPECGROUPKEY and READERGROUPKEY into new sibling TIMESPECGROUPNAME/READERGROUPNAME fields via one full-table GetTimeSpecGroups fetch and one full-table GetReaderGroups fetch per call — a fixed cost regardless of anything else, since a single access level carries exactly one of each key. THREATLEVELGROUPKEY is never resolved (no NBAPI read command exists for threat level groups). Set RESOLVEGROUPNAMES: false to skip both fetches and return the response exactly as GetAccessLevel provides it.
| Name | Required | Description | Default |
|---|---|---|---|
| ACCESSLEVELKEY | Yes | Required. The unique ACCESSLEVELKEY of the access level to retrieve. | |
| RESOLVEGROUPNAMES | No | Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Resolves TIMESPECGROUPKEY/READERGROUPKEY into new TIMESPECGROUPNAME/READERGROUPNAME sibling fields via one full-table GetTimeSpecGroups fetch and one full-table GetReaderGroups fetch per call (each made only when that axis's key is non-empty; an empty/absent key on one axis yields '' for that axis's name without affecting the other). THREATLEVELGROUPKEY is never resolved — no NBAPI read command exists for threat level groups. Set to false to skip both fetches and return the response exactly as GetAccessLevel provides it. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations supply only readOnlyHint=true; the description goes well beyond by disclosing the inverted opt-out default, the exact per-call cost (one full-table GetTimeSpecGroups and one GetReaderGroups fetch), the empty-key edge case, and the fact that THREATLEVELGROUPKEY is never resolved. This is exactly the kind of hidden behavior an agent cannot get from structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded in the opening clause, which is good, but the remainder is a single dense mega-sentence that re-explains RESOLVEGROUPNAMES almost verbatim from the schema. The cost/behavior detail earns its place, but the duplication and lack of sentence breaks hurt readability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description does the heavy lifting on return shape by naming the new TIMESPECGROUPNAME/READERGROUPNAME sibling fields and stating the raw response otherwise passes through. It omits error behavior for an invalid or missing ACCESSLEVELKEY, which keeps it short of a 5.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the schema's RESOLVEGROUPNAMES description already carries the inverted-default note and the resolution mechanics, so the description's parameter content is largely a restatement. It adds no syntax or format detail beyond what the schema already documents, making 3 the correct baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb+resource: 'Returns the details of a single access level for a given ACCESSLEVELKEY'. The singular 'single' plus the explicit key parameter cleanly separates it from the sibling get_access_levels (plural) and get_access_level_names without the agent needing to open any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear guidance on the parameter choice (when to set RESOLVEGROUPNAMES: false to skip fetches), but never states when to call this tool versus the many adjacent siblings (get_access_levels, get_access_level_names, get_access_level_group). The single-vs-list distinction is implied by the name only, so usage is inferred rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_access_level_groupBRead-only
Returns the details of a single access level group for a given ACCESSLEVELGROUPKEY (wraps NBAPI GetAccessLevelGroup).
| Name | Required | Description | Default |
|---|---|---|---|
| ACCESSLEVELGROUPKEY | Yes | Required. The unique ACCESSLEVELGROUPKEY of the access level group to retrieve. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint=true annotation already establishes this is a safe read. The description adds only that it wraps NBAPI GetAccessLevelGroup, with no disclosure of not-found/error behavior, auth requirements, or what 'details' concretely includes. Since annotations carry the safety profile, a 3 is appropriate.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One front-loaded sentence naming the verb, resource, and key parameter. The parenthetical NBAPI provenance note is minor padding but arguably useful for tracing the underlying API.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description should ideally sketch the returned shape, but 'the details' stays vague. For a trivial single-parameter getter with full schema coverage and a read-only annotation, it is minimally adequate rather than complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single parameter is fully documented in the schema, so the baseline is 3. The description names ACCESSLEVELGROUPKEY but adds no format, casing, or sourcing guidance beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Returns) and resource (details of a single access level group) keyed by ACCESSLEVELGROUPKEY. The word 'single' implicitly contrasts with the plural sibling get_access_level_groups, though the sibling is never named explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: 'a single access level group' vs the plural sibling suggests this is the one-at-a-time lookup, but there is no explicit when-to-use, when-not, or named alternative. The agent must infer the routing from the noun number.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_access_level_groupsBRead-only
Lists access level groups configured on the NetBox system (wraps NBAPI GetAccessLevelGroups).
| Name | Required | Description | Default |
|---|---|---|---|
| PARTITIONKEY | No | Optional. Per NBAPI GetAccessLevelGroups — only "0" is documented as allowed. | |
| STARTFROMKEY | No | Optional. Pagination cursor to continue listing from a previous call. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the safe-read profile is covered. The description adds only an implementation note ('wraps NBAPI GetAccessLevelGroups'), which is not behavioral context — no return contents, pagination behavior, or scoping limits are described.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no filler; the resource is identified before the parenthetical implementation detail. Nothing could be trimmed without losing the core statement.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only list endpoint with full parameter coverage and annotation-declared safety, the description is minimally sufficient. It is silent on what a returned group contains and how it relates to singular/related siblings, which would help an agent disambiguate in a very crowded tool set.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the two optional parameters (PARTITIONKEY, STARTFROMKEY) are documented in the schema itself, including the odd constraint that only '0' is allowed. The description adds nothing beyond that, so the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Lists) and resource (access level groups) on the NetBox system, so the agent immediately knows what it retrieves. It does not explicitly contrast with the near-identical sibling get_access_level_group (singular) or get_access_level_names, leaving the plural/singular distinction to be inferred.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use, no prerequisites, and no mention of alternative tools despite several plausible siblings (get_access_level_group, get_access_level_names). The listing nature implies a read use case but nothing is stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_access_level_namesCRead-only
Lists access level names configured on the NetBox system (wraps NBAPI GetAccessLevelNames).
| Name | Required | Description | Default |
|---|---|---|---|
| PARTITIONKEY | No | Optional. Per NBAPI GetAccessLevelNames — only "0" is documented as allowed. | |
| STARTFROMNAME | No | Optional. Pagination cursor (name) to continue listing from a previous call. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true already establishes the safe-read profile, so the description's remaining obligation is to add context it does not: no return shape, no mention that results are paginated via the name cursor, and 'wraps NBAPI GetAccessLevelNames' is an implementation detail rather than agent-useful behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with the verb first and no wasted clauses. The trailing parenthetical about the NBAPI wrapper is of marginal value to an agent but is not bloating.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-arg, read-only list tool with no output schema, the description is minimally adequate but omits the return format and the pagination story implied by STARTFROMNAME. An agent can call it, but not predict what comes back.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so PARTITIONKEY and STARTFROMNAME are already documented in the schema. The description adds nothing about either parameter, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Lists') and resource ('access level names') with the scope qualifier 'configured on the NetBox system'. The 'names' framing partially distinguishes it from siblings like get_access_levels/get_access_level, though it never explicitly contrasts the lightweight name lookup against those fuller-object siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives no when-to-use guidance and does not name any alternative. An agent must infer on its own that this is the lightweight name-discovery call versus get_access_levels, and there is no note about prerequisites or pagination-driven repeated calls (the cursor param lives only in the schema).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_access_levelsCRead-only
Lists access levels configured on the NetBox system (wraps NBAPI GetAccessLevels).
| Name | Required | Description | Default |
|---|---|---|---|
| WANTKEY | No | Optional. Per NBAPI GetAccessLevels. | |
| PARTITIONKEY | No | Optional. Per NBAPI GetAccessLevels — only "0" is documented as allowed. | |
| STARTFROMKEY | No | Optional. Pagination cursor (key) to continue listing from a previous call. | |
| STARTFROMNAME | No | Optional. Pagination cursor (name) to continue listing from a previous call. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the safety profile is covered. The description adds nothing behavioral beyond that: no mention of pagination behavior, authentication requirements, result volume, or what an 'access level' record contains. 'Wraps NBAPI GetAccessLevels' is a source-code note, not operational context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single short sentence with the resource stated up front — no padding. The trailing wrapper parenthetical is marginal but doesn't bloat the definition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only list tool with no output schema and fully documented optional params, the description is minimally adequate. It still leaves an agent guessing how it relates to the several near-identically named access-level siblings and what pagination/return behavior to expect.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the four optional parameters (including both pagination cursors) are already documented in the schema. The description contributes no additional parameter meaning, which is the expected baseline when the schema does the work.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Lists access levels') and scopes it to the NetBox system, which is clear enough to act on. However it does nothing to distinguish itself from close siblings like get_access_level, get_access_level_groups, and get_access_level_names; the parenthetical wrapper note adds implementation detail rather than differentiation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus get_access_level, get_access_level_group(s), or get_access_level_names, nor any stated preconditions. The agent must infer usage from the singular/plural name alone.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_alarmsARead-only
Returns alarm information from the NetBox system, optionally filtered by partition, alarm ID, event ID, activity ID or owner (wraps NBAPI GetAlarms). Responds with an ALARMS/ALARM collection; a system with no active alarms answers with an empty collection or a documented NOT FOUND.
| Name | Required | Description | Default |
|---|---|---|---|
| ID | No | Optional. Return the alarm with this alarm ID. | |
| EVENTID | No | Optional. Return alarms raised by this event ID. | |
| OWNERID | No | Optional. Return alarms owned by this operator/person ID. | |
| ACTIVITYID | No | Optional. Return alarms for this activity ID. | |
| PARTITIONKEY | No | Optional. Return alarms for this partition key only. | |
| ALLPARTITIONS | No | Optional. "TRUE" to return alarms across all partitions. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already confirms this is a safe read. The description adds useful behavior beyond annotations: the response is an ALARMS/ALARM collection, and a system with no active alarms returns an empty collection or a documented NOT FOUND. This clarifies return format and error handling, though it omits permission or pagination details.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is largely front-loaded and efficient, stating the purpose first and then covering filters and response behavior. It is a bit dense with parentheses and a semicolon, but every clause contributes useful information with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description must cover return values, which it does by describing the ALARMS/ALARM collection and the empty/NOT FOUND case. Combined with the fully documented input schema and readOnly annotation, the definition is complete enough for correct invocation, though it could mention authentication or pagination if relevant.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so each of the six optional parameters is fully documented in the schema. The description lists the same filters (partition, alarm ID, event ID, activity ID, owner) but adds no syntax or format detail beyond what the schema already provides, so the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Returns alarm information from the NetBox system.' It also identifies the underlying API wrapper (NBAPI GetAlarms) and the available filters, making it clearly distinguishable from all sibling tools, which deal with access control, network nodes, and events rather than alarms.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage by stating it returns alarm information with optional filters, but it does not explicitly say when to use this tool versus alternatives. No exclusions or alternative tools are named, so the agent must infer that this is the correct tool for retrieving alarms.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_card_access_detailsARead-only
Returns card/credential access details for a given card (wraps NBAPI GetCardAccessDetails). Identifies the card by ENCODEDNUM + CARDFORMAT, not PERSONID — GetCardAccessDetails has no PERSONID parameter. RESOLVEDESCRIPTIONS defaults to true — an inverted, opt-out default (unlike most optional booleans in this codebase): each returned ACCESS record is enriched with the reader's human-readable READERDESCRIPTION via one GetReaders full-table fetch per call (not per record); set RESOLVEDESCRIPTIONS: false to skip it. Set RESOLVENAMES: true to enrich the response with the card owner's FIRSTNAME/LASTNAME/FULLNAME/NOTES (default false — off) via a single GetPerson lookup for the card's one PERSONID — cheaper than get_access_history's RESOLVENAMES, which pays one GetPerson call per distinct person across many records, since a card has exactly one owner. The four fields land on the top level of the response, alongside PERSONID/DISABLED/EXPDATE, not on each ACCESS record.
| Name | Required | Description | Default |
|---|---|---|---|
| CARDFORMAT | Yes | Required. The card format of ENCODEDNUM. | |
| ENCODEDNUM | Yes | Required. The encoded card number whose access details should be retrieved. | |
| MAXRECORDS | No | Optional. Maximum number of access records to return. | |
| OLDESTDTTM | No | Optional. Oldest date/time to include in the returned access records. | |
| RESOLVENAMES | No | Optional (default false). Enrich the top level of the response with the card owner's FIRSTNAME/LASTNAME/FULLNAME/NOTES via a single GetPerson lookup for the response's top-level PERSONID (one lookup per call, not one per ACCESS record — a card has exactly one owner). | |
| RESOLVEDESCRIPTIONS | No | Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Enrich each returned ACCESS record with the reader's human-readable READERDESCRIPTION via one GetReaders full-table fetch per call (not per record). Set to false to skip it. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint=true, so the description carries real weight and delivers: the inverted, opt-out RESOLVEDESCRIPTIONS default, the exact cost model (one GetReaders full-table fetch per call, not per record), the off-by-default RESOLVENAMES behavior, and where the enriched fields land in the response. This is behavioral detail an agent cannot get from the annotations or schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and lookup-key constraint are front-loaded, and most sentences carry non-obvious information. It is a dense single block, though, and the RESOLVENAMES/RESOLVEDESCRIPTIONS passages substantially duplicate the schema descriptions, which slightly dilutes signal per word.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description does the work of explaining return shape ('the four fields land on the top level... alongside PERSONID/DISABLED/EXPDATE') plus the enrichment and cost behavior—enough to call it correctly. It does not cover pagination semantics of MAXRECORDS or empty/error results, so it is not fully exhaustive.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3, but the description adds genuine meaning beyond the schema by explaining why RESOLVEDESCRIPTIONS is on by default and what each enrichment costs. The RESOLVENAMES/RESOLVEDESCRIPTIONS sentences largely restate schema text, but the performance rationale and output-placement notes are additive.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns card/credential access details for a given card') and immediately scopes the lookup key ('Identifies the card by ENCODEDNUM + CARDFORMAT, not PERSONID'), which distinguishes it from person-centric siblings. It also names the underlying primitive (GetCardAccessDetails) and its limitation, so an agent can route correctly without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear context for the RESOLVENAMES choice by contrasting it with get_access_history's costlier per-person lookup, which helps an agent pick the cheaper tool. However it never states an explicit when-to-use/when-not for the tool as a whole versus siblings like get_access_history or get_reader_access_history; the selection guidance is implied rather than spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_card_formatsBRead-only
Returns the card formats configured on the NetBox system (wraps NBAPI GetCardFormats). No parameters required.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true already tells the agent this is a safe read. The description adds only 'wraps NBAPI GetCardFormats,' an internal implementation detail of no decision value, and says nothing about scope, completeness of the returned set, or pagination.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single short sentence with the core purpose front-loaded. The trailing parenthetical about the NBAPI wrapper is dead weight but does not obscure the main claim.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple and parameterless, so little is required, but with no output schema the description could have indicated what a 'card format' record contains or whether the list is complete or paginated. It is minimally adequate rather than complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so per the rubric the baseline is 4. The description correctly notes no parameters are required, matching the empty schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: returns the card formats configured on the system. The resource is distinct enough from siblings like get_card_access_details, but the description never explicitly contrasts itself with any alternative, so it stops short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus siblings such as get_card_access_details or get_access_levels. 'No parameters required' is a fact about invocation, not a usage condition, so the agent gets no routing help.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_daily_unlock_windowCRead-only
Reports the managed daily recurring unlock window, if any: the managed portal group "MCP Daily Unlock Window" (key, portals, its unlock time spec group and whether that is the managed time spec group "MCP Daily Unlock Window time specs"), that group and its members (read from paginated GetTimeSpecGroups), the one managed time spec and holiday, the window derived from them, and whether it is active right now on the host clock. Read-only: issues only Get commands. No parameters required.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so 'Read-only: issues only Get commands' is largely redundant. The description does add useful behavioral context by noting it reads from paginated GetTimeSpecGroups and describing the components of the returned window plus a live active-now check, which is meaningful given there is no output schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single bloated, comma-spliced sentence with parenthetical nesting and repeated string literals ('MCP Daily Unlock Window' appears twice). It is poorly front-loaded and hard to parse, with the key purpose obscured by the enumeration of internals.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description must carry the burden of describing return content, and it does attempt to enumerate what is reported. But the enumeration is confusing and omits return shape/pagination details, so it is only partially adequate for a tool whose value lies in its response.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, which is the baseline-4 case. The description's 'No parameters required' correctly matches the empty input schema, so nothing is ambiguous.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
It names a specific verb (Reports) and resource (managed daily recurring unlock window) and enumerates the constituent parts. However, the purpose is buried in a run-on sentence with nested quotes, and it never distinguishes itself from the closely related sibling get_unlock_window, so an agent cannot easily tell the two apart.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use guidance and no mention of the alternative get_unlock_window or the time-spec/holiday siblings. The only conditional is the vague 'if any', leaving the agent to infer selection criteria.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_elevatorsBRead-only
Lists elevators configured on the NetBox system (wraps NBAPI GetElevators).
| Name | Required | Description | Default |
|---|---|---|---|
| STARTFROMKEY | No | Optional. Pagination cursor to continue listing from a previous call. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint=true annotation already establishes this as a safe read operation, so the description is not carrying the safety burden. Beyond that it only names the upstream wrapper ('NBAPI GetElevators'), which is implementation provenance rather than useful behavioral context such as pagination behavior or result scope; it is consistent with the annotation but adds little.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One front-loaded sentence with the action and resource first; no padding or restated title. The parenthetical wrapper reference is marginally useful at best but does not undermine the sentence.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only list tool with one fully documented optional parameter and no output schema, the definition is minimally viable. It still omits what the listing returns and how result sets interact with the cursor, which forces the agent to discover pagination semantics empirically.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single optional STARTFROMKEY cursor is fully documented in the schema. The description adds no pagination or format meaning beyond that, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a clear verb ('Lists') and resource ('elevators') plus the system scope ('configured on the NetBox system'), which is enough to separate it from unrelated siblings like get_readers or get_holidays. It does not differentiate itself from adjacent structural resources such as get_floors or get_partitions, but the resource noun is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use guidance and no alternative named. Nothing tells the agent whether this is the right call versus get_floors/get_mercury_panels or when a single-elevator lookup would be preferred; usage must be inferred entirely from the name.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_event_historyBRead-only
Returns historical NetBox events for an optional event name/date range (wraps NBAPI GetEventHistory).
| Name | Required | Description | Default |
|---|---|---|---|
| ENDDTTM | No | Optional. End of the date/time range to query (NBAPI-documented format). | |
| NEXTKEY | No | Optional. Pagination continuation cursor from a previous call. | |
| EVENTNAME | No | Optional. Restrict results to this event name. | |
| STARTDTTM | No | Optional. Start of the date/time range to query (NBAPI-documented format). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the safety profile is covered; the description is consistent with it ('historical' implies an immutable read). It adds modest context by noting the wrapper origin (NBAPI GetEventHistory) and date-range scoping, but says nothing about pagination behavior, result limits, or what an empty range returns.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence that names the operation, the returned resource, and the filter axes with no filler. The parenthetical wrapper note is the only marginal element, but it is short enough not to hurt.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple optional-filter history read, the annotations plus a fully-described schema cover most of what an agent needs. However, with no output schema, the description should at least hint at the returned event shape and pagination semantics, which it does not.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% with four optional, self-documented parameters, so the schema already carries the load. The description reinforces EVENTNAME and the date range but adds no format detail (e.g., what 'NBAPI-documented format' actually is, or how NEXTKEY interacts with the range) beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns historical NetBox events') plus the optional filter dimensions (event name/date range), so the function is immediately understandable. It does not, however, differentiate itself from the sibling 'list_events' or the various access-history tools, leaving the agent to guess the boundary.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No when-to-use or when-not-to-use guidance is given. With siblings like list_events, get_access_history, and get_reader_access_history present, the description never says which one to pick or under what conditions, so routing is left entirely to inference from the name.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_floorsBRead-only
Lists floors configured on the NetBox system (wraps NBAPI GetFloors).
| Name | Required | Description | Default |
|---|---|---|---|
| STARTFROMKEY | No | Optional. Pagination cursor to continue listing from a previous call. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the safety profile is covered. The description adds only the internal note "wraps NBAPI GetFloors", which is implementation trivia rather than behavioral context; pagination behavior implied by STARTFROMKEY is never mentioned.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One short, front-loaded sentence with the verb and resource up front and no filler. The parenthetical wrapper reference is the only element that arguably does not earn its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only list tool with one optional, fully documented parameter, the description is minimally adequate. With no output schema, it could have said more about return shape or whether pagination is supported beyond the cursor.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single STARTFROMKEY parameter has 100% schema description coverage, so the schema fully documents it as a pagination cursor. The description adds nothing beyond that, which is the expected baseline when schema coverage is complete.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ("Lists") and resource ("floors") plus the owning system (NetBox), so the operation is unambiguous. It does not distinguish itself from the many other `get_*`/list siblings, so it stops short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives no when-to-use guidance, no prerequisites, and no alternatives among the many sibling list tools (get_locations, get_elevators, etc.). It only implies usage through the word "Lists", which is not actionable routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_guideARead-only
Returns static reference guidance on this server’s S2 NetBox domain knowledge. Makes no controller call. Call with no topic for an index of all six topics; call with topic set to one of access-model, unlock-windows, group-and-name-gotchas, credentials-and-card-formats, api-quirks, write-safety for that topic’s full content.
| Name | Required | Description | Default |
|---|---|---|---|
| topic | No | Optional. One of: access-model, unlock-windows, group-and-name-gotchas, credentials-and-card-formats, api-quirks, write-safety. Omitted or unrecognized returns the index instead. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so safety is covered. The description adds meaningful behavioral context on top: it makes no controller call (so no live state changes or network dependency) and it discloses the fallback behavior for omitted or invalid topics. It stops short of describing content depth or size per topic.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the key distinction (static, no controller call) before the usage detail. Minor redundancy in re-enumerating all six topic names already present in the schema, but the structure is tight and skimmable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description carries the return-value burden, and it does indicate the two response shapes (an index vs. full topic content). It could say more about what a topic's content covers or its approximate size, but for a one-parameter static lookup this is close to complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the schema already documents the optional topic parameter, the six valid values, and the omitted/unrecognized fallback. The description largely restates that same list, adding no format or semantic detail beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Returns) and resource (static reference guidance on this server's S2 NetBox domain knowledge), and immediately distinguishes itself from the many controller-calling siblings by noting 'Makes no controller call.' An agent can tell this is a local knowledge lookup, not a data fetch, without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly covers both invocation modes: no topic returns an index, a topic set to one of the six named values returns that topic's content. It also flags the non-obvious fallback for unrecognized topics, which is exactly the kind of when/when-not guidance that prevents misuse.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_holidayARead-only
Returns the details of a single holiday for a given HOLIDAYKEY (wraps NBAPI GetHoliday).
| Name | Required | Description | Default |
|---|---|---|---|
| HOLIDAYKEY | Yes | Required. The unique HOLIDAYKEY of the holiday to retrieve. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the safety profile is covered and the bar is lower. The description adds the detail that it wraps NBAPI GetHoliday, but says nothing about failure behavior when the key is unknown or the shape of the returned details. Adequate but thin beyond the annotation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence identifying purpose, scope, and the underlying API call; nothing is wasted. It could arguably drop the NBAPI parenthetical, but that is minor.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only single-record lookup with a fully documented single parameter and annotation-covered safety, the description supplies enough to call the tool correctly. The only real gap is the absence of error/not-found behavior, which is a minor omission for this tool shape.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 100% schema coverage and only one parameter, the schema already states that HOLIDAYKEY is required and unique. The description restates its role (identifying the holiday) without adding format, case sensitivity, or example values, so baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Returns the details of a single holiday") and scopes it to one record via HOLIDAYKEY, which implicitly separates it from the sibling get_holidays plural listing. It stops short of naming the sibling explicitly, so differentiation relies on the word "single" rather than a direct contrast.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the required key: an agent infers this is the tool to call when it already has a HOLIDAYKEY and wants one record. There is no explicit when-to-use/when-not statement and no pointer to get_holidays for enumerating holidays, so the routing guidance is only implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_holidaysARead-only
Lists holiday keys configured on the NetBox system (wraps NBAPI GetHolidays). Returns a comma-separated key string, not a list of records — use get_holiday per key for details. GetHolidays has no documented calling parameters (no pagination).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so safety is covered. The description still adds meaningful behavior: the return is a comma-separated key string rather than records, and there is no pagination, which prevents an agent from attempting offset/limit handling.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, each carrying distinct load: purpose, return shape plus sibling routing, and parameter/pagination absence. The most decision-relevant fact (key string, not records) is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description fills the gap by describing the return format and the absence of pagination; with zero params and readOnlyHint, nothing else is needed to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so the baseline is 4. The description confirms this explicitly ('no documented calling parameters (no pagination)'), which is useful confirmation rather than mere silence.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Lists holiday keys configured on the NetBox system') and explicitly differentiates from the sibling get_holiday by describing itself as a key-only listing. An agent can tell it apart from get_holiday without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Names the alternative ('use get_holiday per key for details') and the condition that selects it, which is clear routing guidance. It stops short of a full when/when-not statement, but the practical fork is unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_locationsARead-only
Lists the locations configured on the NetBox system (wraps NBAPI GetLocations). Each record carries LOCATIONKEY, PARTITIONKEY and a nested PARENTLOCATION { PARENTKEY, NAME } block, so the result describes a location tree. LOCATIONKEY values feed get_portal_statuses and set_threat_level.
| Name | Required | Description | Default |
|---|---|---|---|
| STARTFROMKEY | No | Optional. Pagination cursor to continue listing from a previous call. (Not in the guide's Calling Parameters list, but its documented FAIL messages include "Invalid STARTFROMKEY".) | |
| ALLPARTITIONS | No | Optional. "TRUE" to list locations across all partitions. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the safety profile is covered. The description goes further and discloses the concrete record shape (LOCATIONKEY, PARTITIONKEY, nested PARENTLOCATION block implying a tree), which is real behavioral context, though it says nothing about pagination behavior or ordering.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three front-loaded sentences with no filler; purpose comes first, then return shape, then downstream consumers. The parenthetical 'wraps NBAPI GetLocations' is the only marginally expendable content, so not quite a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description usefully carries the return-value burden by describing record fields and the location-tree structure. Pagination and partition-scoping behavior are left to the schema, but nothing needed to call the tool correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters (STARTFROMKEY cursor, ALLPARTITIONS enum) are already documented in the schema. The description adds no syntax or semantics beyond that, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Lists the locations configured on the NetBox system') and adds the underlying API call (NBAPI GetLocations) for traceability. It is clearly distinguishable from sibling list tools like get_portals or get_partitions, though it never explicitly names what it is not.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It hints at downstream use by noting that LOCATIONKEY values feed get_portal_statuses and set_threat_level, which implies a reason to call it. However, it gives no when-to-use/when-not guidance, no prerequisites, and no comparison against alternatives such as get_partitions or get_floors.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_mercury_panelARead-only
Returns the details of a single Mercury panel for a given MERCURYKEY (wraps NBAPI GetMercuryPanel).
| Name | Required | Description | Default |
|---|---|---|---|
| MERCURYKEY | Yes | Required. The key of the Mercury panel to retrieve. Use get_mercury_panels to discover keys. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint=true annotation already establishes this as a safe read, and the description adds only the fact that it wraps 'NBAPI GetMercuryPanel,' which gives the agent no actionable behavior. No error modes, response shape, or lookup-failure behavior is disclosed, so it contributes little beyond the annotation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with zero padding; the verb+resource+identifier scope come first and nothing is restated.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter, read-only lookup with no output schema, the description is adequate: the agent knows what it takes and roughly what it gets. It would be complete at 5 if it hinted at the returned detail or how a missing key behaves, but that is a minor gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single MERCURYKEY parameter is fully documented in the schema, including the discovery hint for get_mercury_panels. The description adds no parameter meaning beyond restating the key requirement, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb ('Returns the details') and resource ('a single Mercury panel'), and the qualifier 'single' clearly distinguishes it from the plural sibling get_mercury_panels. An agent can select it without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: you call this when you need details of one panel and already hold its key. There is no explicit statement of when this is preferable to get_mercury_panels, though the schema's parameter text ('Use get_mercury_panels to discover keys') partially compensates.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_mercury_panelsARead-only
Lists the Mercury panels configured on the NetBox system, optionally filtered (wraps NBAPI GetMercuryPanels). Returns an empty collection (or a FAIL/"NOT FOUND") on a system with no Mercury hardware.
| Name | Required | Description | Default |
|---|---|---|---|
| NAME | No | Optional. Restrict the listing to Mercury panels with this name. | |
| MERCURYKEY | No | Optional. Restrict the listing to this Mercury panel key. | |
| PARTITIONKEY | No | Optional. Restrict the listing to this partition key. | |
| ALLPARTITIONS | No | Optional. "TRUE" to list Mercury panels across all partitions. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With readOnlyHint=true already declaring the safety profile, the description adds real behavioral context beyond annotations: it discloses the empty-result case and the FAIL/'NOT FOUND' outcome when no Mercury hardware exists, which prepares the agent for error-shaped responses. It could go further on permission needs or result shape, but the failure-mode disclosure is genuinely useful.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, front-loaded with the core action and followed immediately by the edge-case behavior. No filler, no restatement of the tool name.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, and the description covers the tricky return case (empty or FAIL/NOT FOUND), which is the main thing an agent needs to interpret results. It stops short of describing what a populated panel entry looks like, but for a simple read-only listing tool this is nearly complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so NAME, MERCURYKEY, PARTITIONKEY and ALLPARTITIONS are all documented in the schema with their semantics. The description only says 'optionally filtered' and adds nothing about filter interaction (e.g. whether ALLPARTITIONS overrides PARTITIONKEY), so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Lists the Mercury panels configured on the NetBox system') and even names the underlying call it wraps, so the agent knows exactly what it does. However, it never distinguishes itself from the singular sibling get_mercury_panel, leaving the list-vs-single choice to inference.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'optionally filtered' hints that filters exist but gives no guidance on when to filter versus list everything, and no prerequisites. It also never points to get_mercury_panel as the alternative for retrieving one panel, despite that sibling being an obvious source of confusion.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_network_nodeBRead-only
Returns the details of a single network node for a given NODEKEY (wraps NBAPI GetNetworkNode).
| Name | Required | Description | Default |
|---|---|---|---|
| NODEKEY | Yes | Required. The key of the network node to retrieve. Use get_network_nodes to discover keys. | |
| PARTITIONKEY | No | Optional. Key of the partition the node belongs to. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true already establishes this as a safe read, so the bar is lower. The description adds only the implementation note "(wraps NBAPI GetNetworkNode)", which is not behaviorally useful to an agent — it says nothing about error handling for missing nodes or the shape of the returned details.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no redundancy. The parenthetical wrapper note is mild filler but doesn't disrupt the structure.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only getter with fully documented parameters this is adequate, but with no output schema the description never indicates what 'details' a node actually contains, leaving the agent guessing at the response.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, including the discovery tip for NODEKEY, so the schema does the heavy lifting and baseline 3 applies. The description's "for a given NODEKEY" adds no syntax or format detail beyond what the schema already provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ("Returns") and resource ("details of a single network node") keyed on NODEKEY, which cleanly distinguishes it from the plural sibling get_network_nodes. It stops short of naming that sibling explicitly, so it falls just shy of the top tier.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by "a single network node" — retrieve one node versus the plural listing — but the description itself offers no explicit when-to-use or when-not-to-use guidance. The routing hint to get_network_nodes lives in the schema, not the description.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_network_nodesARead-only
Lists the network nodes configured on the NetBox system, optionally filtered (wraps NBAPI GetNetworkNodes).
| Name | Required | Description | Default |
|---|---|---|---|
| NAME | No | Optional. Restrict the listing to network nodes with this name. | |
| NODEKEY | No | Optional. Restrict the listing to this network node key. | |
| PARTITIONKEY | No | Optional. Restrict the listing to this partition key. | |
| ALLPARTITIONS | No | Optional. "TRUE" to list network nodes across all partitions. | |
| UNIQUEIDENTIFIER | No | Optional. Restrict the listing to the node with this 16-hex-digit unique identifier. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true already signals a safe read, so the bar is lower. The description adds the optional-filter behavior and the underlying NBAPI wrapper, but does not disclose pagination, return volume, or output shape.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single efficient sentence, front-loaded with the verb and resource. The parenthetical wrapper note is parenthetical and not distracting.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Adequate for a simple read-only listing with no output schema, but leaves pagination, volume, and default-partition behavior unstated, which matter for a list operation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all five parameters including the ALLPARTITIONS enum are already documented. The description only states filtering is optional, adding no syntax or format detail beyond the schema; baseline 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Lists) and resource (network nodes) with scope (configured on the NetBox system, optionally filtered). Distinguishable from the singular sibling get_network_node by explicitly being a listing.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Implied usage is a filtered listing of network nodes, but there is no explicit when-to-use/when-not guidance or routing to alternatives like the singular get_network_node for a single node.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_outputsCRead-only
Lists auxiliary outputs configured on the NetBox system (wraps NBAPI GetOutputs).
| Name | Required | Description | Default |
|---|---|---|---|
| STARTFROMKEY | No | Optional. Pagination cursor to continue listing from a previous call. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the safety profile is covered. The description adds almost nothing beyond that: it does not say what an 'auxiliary output' is, whether results are filtered or scoped, or how pagination terminates, and the 'wraps NBAPI GetOutputs' note is implementation trivia rather than agent-relevant behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single short sentence, front-loaded with the verb and resource. The trailing parenthetical is dead weight but the description does not ramble.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only list tool with one fully documented optional param and no output schema, the definition is structurally adequate. It stops short of defining the domain term 'auxiliary outputs' or indicating result scope, which is the main thing an agent would still wonder about.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With one optional parameter and 100% schema description coverage, the schema already explains STARTFROMKEY as a pagination cursor. The description contributes no additional parameter meaning, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Lists') and resource ('auxiliary outputs configured on the NetBox system'), which is clearer than a bare name restatement. It does not, however, distinguish itself from the many other list-style siblings (get_portals, get_readers, list_events) beyond the resource noun, and the parenthetical '(wraps NBAPI GetOutputs)' adds no selection information.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use guidance, no prerequisites, and no mention of alternatives among the sibling list tools. The only usage-adjacent hint (pagination) lives in the schema, not the description.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_partitionsBRead-only
Lists partitions configured on the NetBox system (wraps NBAPI GetPartitions). No parameters required.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true already tells the agent this is a safe read. The description adds only the implementation note 'wraps NBAPI GetPartitions' and the redundant 'No parameters required', disclosing nothing about return shape, ordering, or pagination. It does not contradict the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no padding. It is efficient, though arguably too terse to be informative.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a trivial zero-parameter read tool this is close to sufficient, and the readOnly annotation carries the safety profile. With no output schema, however, the description could reasonably say what a partition record contains, and it offers no usage context against the large sibling set.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so per the rubric the baseline is 4; there is nothing for the description to clarify beyond the (accurate) statement that none are required.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Lists) and resource (partitions) plus the source system (NetBox), so an agent knows exactly what it retrieves. There is no partition-related sibling to differentiate from, but the description never routes the agent against the many other get_* list tools in the family.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description says only that no parameters are required; it gives no context on when to call this versus the dozens of other get_* list tools, nor any prerequisites. Usage is left entirely to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_personBRead-only
Returns the full person record for a given PERSONID (wraps NBAPI GetPerson).
| Name | Required | Description | Default |
|---|---|---|---|
| PERSONID | Yes | Required. The unique PERSONID of the person record to retrieve. | |
| ALLPARTITIONS | No | Optional. Per NBAPI GetPerson. | |
| WANTCREDENTIALID | No | Optional. Include CREDENTIALID values on returned access cards. | |
| ACCESSLEVELDETAILS | No | Optional. Include full access level details in the response. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already establishes that this is a safe read, lowering the bar. The description contributes the scope of the return ('full person record') and traces provenance to the underlying NBAPI GetPerson call, but says nothing about what happens when the PERSONID does not exist or what fields the full record contains.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence with zero filler, front-loading the return value and the required key before the parenthetical provenance note. Nothing is redundant and nothing needs trimming.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description carries some burden for describing the returned record; 'full person record' only gestures at it. For a simple ID lookup with a fully documented schema and a read-only annotation, this is adequate but leaves the return shape and not-found behavior unexplained.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all four parameters — including the optional ALLPARTITIONS, WANTCREDENTIALID, and ACCESSLEVELDETAILS flags — are already documented in the schema. The description mentions only PERSONID and adds no format or constraint detail beyond it; baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource — 'Returns the full person record for a given PERSONID' — which is unambiguous and names the exact identifier needed. It does not differentiate itself from the sibling search_person_data, which is the closest adjacent tool, so it falls short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied by the presence of the PERSONID parameter; there is no explicit statement of when to use this lookup versus search_person_data or other person-related retrievals. No exclusions or prerequisites are given.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_pictureARead-only
Returns a person's photo ID image (wraps NBAPI GetPicture). The response's PICTURE field is a Base64-encoded JPEG and may be very large — a full-size badge photo runs to hundreds of kilobytes of Base64 text, so prefer PICTUREURL when you only need to identify the file. The response also carries PERSONID, PICTUREURL, LASTNAME, FIRSTNAME and LASTMOD. The payload is passed through unmodified: this tool neither decodes, resizes nor truncates the Base64 string. A person with no photo answers FAIL with "No picture URL for this person ID" or "Picture file does not exist"; an oversized image answers "Person picture image file size exceeds maximum to be returned/exported".
| Name | Required | Description | Default |
|---|---|---|---|
| PERSONID | Yes | Required. The PERSONID of the person whose picture should be retrieved. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint, it discloses payload size ('hundreds of kilobytes of Base64'), the pass-through behavior ('neither decodes, resizes nor truncates'), and verbatim FAIL messages for missing and oversized photos. This is exactly the context annotations cannot carry and it materially changes how an agent should call the tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the purpose, then size warning, then return fields, then error cases — a logical order with no filler. Some sentences are long and the field list borders on restating the response, but each element carries information an agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description compensates fully by enumerating the returned fields (PICTURE, PERSONID, PICTUREURL, LASTNAME, FIRSTNAME, LASTMOD), the PICTURE encoding, and every known failure mode. Nothing needed to call or interpret the call is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single PERSONID parameter is already fully documented in the schema at 100% coverage, so the description adds nothing about parameter format or accepted values. Baseline 3 applies; the 'no picture URL for this person ID' failure note is error behavior, not parameter semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns a person's photo ID image') plus the underlying API it wraps, and the size/prefer-PICTUREURL note makes it immediately distinguishable from generic person getters like get_person. An agent knows exactly what this returns without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly advises 'prefer PICTUREURL when you only need to identify the file', giving a clear condition for avoiding the heavyweight call, and names the failure cases (no photo, oversized image). It does not compare against sibling tools such as get_person or search_person_data, so it stops short of full alternative routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_portal_groupARead-only
Returns the details of a single portal group for a given PORTALGROUPKEY (wraps NBAPI GetPortalGroup). RESOLVEGROUPNAMES defaults to true — an inverted, opt-out default (unlike most optional booleans in this codebase): the raw response carries only a bare UNLOCKTIMESPECGROUPKEY foreign key, so this resolves it into a new sibling UNLOCKTIMESPECGROUPNAME field via one full-table GetTimeSpecGroups fetch per call — a fixed cost regardless of anything else, since a single portal group carries exactly one UNLOCKTIMESPECGROUPKEY. THREATLEVELGROUPKEY is never resolved (no NBAPI read command exists for threat level groups). Set RESOLVEGROUPNAMES: false to skip the fetch and return the response exactly as GetPortalGroup provides it.
| Name | Required | Description | Default |
|---|---|---|---|
| PORTALGROUPKEY | Yes | Required. The unique PORTALGROUPKEY of the portal group to retrieve. | |
| RESOLVEGROUPNAMES | No | Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Resolves UNLOCKTIMESPECGROUPKEY into a new UNLOCKTIMESPECGROUPNAME sibling field via one full-table GetTimeSpecGroups fetch per call (skipped when UNLOCKTIMESPECGROUPKEY is empty/absent, yielding '' for the name). THREATLEVELGROUPKEY is never resolved — no NBAPI read command exists for threat level groups. Set to false to skip the fetch and return the response exactly as GetPortalGroup provides it. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With readOnlyHint already covering the safety profile, the description adds substantial behavior: an inverted opt-out default, a per-call full-table GetTimeSpecGroups fetch with a fixed cost, the new sibling field produced, and the explicit non-resolution of THREATLEVELGROUPKEY. This is well beyond what the annotation conveys.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded and every clause carries information (default, cost, exclusion), so there is little waste. It is dense, though, with heavy em-dash and parenthetical nesting that would read better as short bullet points.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given no output schema, the description still sketches return behavior by naming the added UNLOCKTIMESPECGROUPNAME field and what is left unresolved. It omits error behavior for a bad key and does not enumerate the returned fields, but for a two-parameter read tool with readOnlyHint it is otherwise complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so a 3 is the baseline; the description earns above that by explaining the surprising inverted default, the concrete cost of the resolution step, and the fixed-cost reasoning tied to the single-UNLOCKTIMESPECGROUPKEY relationship. It adds real meaning beyond the schema's own text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns the details of a single portal group for a given PORTALGROUPKEY') and pins the underlying operation ('wraps NBAPI GetPortalGroup'). The word 'single' implicitly but effectively distinguishes it from the plural sibling get_portal_groups.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives strong guidance on when to set RESOLVEGROUPNAMES true vs false, including the cost tradeoff. However, it never names an alternative tool (e.g. get_portal_groups for listing, or find_portals) or states when to reach for this tool over its siblings, so tool-selection guidance is only implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_portal_groupsARead-only
Lists portal groups configured on the NetBox system (wraps NBAPI GetPortalGroups). RESOLVEGROUPNAMES defaults to true — an inverted, opt-out default (unlike most optional booleans in this codebase): each returned group's UNLOCKTIMESPECGROUPKEY is a bare foreign key, so this resolves it into a new sibling UNLOCKTIMESPECGROUPNAME field on every group. Costs at most one full-table GetTimeSpecGroups fetch per call — not per group — built once and skipped entirely when every group on the page has an empty/absent UNLOCKTIMESPECGROUPKEY. THREATLEVELGROUPKEY is never resolved (no NBAPI read command exists for threat level groups). Set RESOLVEGROUPNAMES: false to skip the fetch and return groups exactly as GetPortalGroups provides them.
| Name | Required | Description | Default |
|---|---|---|---|
| STARTFROMKEY | No | Optional. Pagination cursor to continue listing from a previous call. | |
| RESOLVEGROUPNAMES | No | Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Resolves each group's UNLOCKTIMESPECGROUPKEY bare foreign key into a new sibling UNLOCKTIMESPECGROUPNAME field, via at most one full-table GetTimeSpecGroups fetch per call (not per group) — built once and skipped entirely when every group on the page has an empty/absent UNLOCKTIMESPECGROUPKEY, yielding '' for the name. THREATLEVELGROUPKEY is never resolved — no NBAPI read command exists for threat level groups. Set to false to skip the fetch and return groups exactly as GetPortalGroups provides them. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With readOnlyHint already covering the safety profile, the description goes well beyond the annotations: it discloses the inverted opt-out default, the at-most-one-full-table GetTimeSpecGroups fetch per call (not per group), the skip path when all keys are empty, the injected sibling field, and the fact that THREATLEVELGROUPKEY is never resolved. That is unusually rich behavioral context for a read tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded in sentence one, but the remainder is a dense single block that largely duplicates the schema's RESOLVEGROUPNAMES text. Much of the length re-covers material already structured elsewhere rather than earning new space.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter read tool with full schema coverage and no output schema, the description is nearly complete on the resolution behavior and its cost. It omits any mention of pagination or how to continue via STARTFROMKEY, which is the one notable gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, and the description's account of RESOLVEGROUPNAMES is essentially a restatement of the schema's own parameter description. It adds framing but little genuinely new parameter meaning beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence gives a specific verb+resource ('Lists portal groups configured on the NetBox system') and names the underlying API call. It is distinguishable from the singular get_portal_group and from get_portals, though it never states that distinction explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied. The description explains when to flip RESOLVEGROUPNAMES to false, but gives no explicit guidance on when to choose this over siblings like get_portal_group or find_portals, nor any prerequisite conditions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_portalsARead-only
Lists portals (doors) configured on the NetBox system, each with its nested readers (wraps NBAPI GetPortals, paginated via STARTFROMKEY/NEXTKEY — there is no single-portal filter). RESOLVEDESCRIPTIONS defaults to true — an inverted, opt-out default (unlike most optional booleans in this codebase): GetPortals never populates a nested reader's own DESCRIPTION field (only READERKEY/NAME/PORTALORDER), so this fills it in directly on each nested reader via one GetReaders full-table fetch per call (not per portal/reader). Set RESOLVEDESCRIPTIONS: false to return readers exactly as GetPortals provides them, with no DESCRIPTION field and no GetReaders call.
| Name | Required | Description | Default |
|---|---|---|---|
| STARTFROMKEY | No | Optional. Pagination cursor — the NEXTKEY from a previous call, to continue listing. | |
| RESOLVEDESCRIPTIONS | No | Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Fills in each nested reader's own DESCRIPTION field (GetPortals leaves it unpopulated) via one GetReaders full-table fetch per call (not per portal/reader). Set to false to skip it and return readers exactly as GetPortals provides them. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations carry only readOnlyHint, and the description adds substantive behavioral context beyond it: the inverted opt-out default (unlike other optional booleans in the codebase), the single GetReaders full-table fetch per call rather than per reader, and the exact consequence of opting out (no DESCRIPTION field, no extra call). This is exactly the kind of cost/side-effect disclosure an agent needs before invoking.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose and flows logically into pagination then the RESOLVEDESCRIPTIONS nuance. It is dense and heavily parenthesized, and the two schema fields repeat much of the same detail, but every sentence carries real information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description compensates by telling the agent what the return payload looks like (portals with nested readers, reader fields READERKEY/NAME/PORTALORDER, optionally DESCRIPTION) and how pagination terminates. For a two-parameter read tool, nothing needed to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters are already documented. The description goes further by explaining the rationale for the unusual inverted default and the performance tradeoff of RESOLVEDESCRIPTIONS (one full-table fetch), which meaningfully supplements the schema text without contradicting it.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb+resource ('Lists portals (doors) configured on the NetBox system') and immediately scopes it with nested readers and pagination semantics. The note that there is 'no single-portal filter' implicitly distinguishes it from find_portals and the singular get_* siblings, so an agent can tell what this returns without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete when-to guidance for RESOLVEDESCRIPTIONS ('set to false to return readers exactly as GetPortals provides them') and explains how STARTFROMKEY continues listing. It stops short of naming sibling alternatives (find_portals, get_portal_group) or saying when this is the wrong tool, so it is clear context without exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_portal_statesARead-only
Lists the portal states the NetBox system defines (wraps NBAPI GetPortalStates). Returns STATEKEY/STATENAME pairs — the vocabulary get_portal_statuses reports against, and the values its STATEKEY filter accepts.
| Name | Required | Description | Default |
|---|---|---|---|
| PORTALSTATES | No | Optional. "TRUE" returns portal states across all partitions; omitted, the controller returns states for the default or currently switched partition. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only assert readOnlyHint=true; the description adds the upstream origin (wraps NBAPI GetPortalStates) and, more usefully, the exact return structure since no output schema exists. It does not cover auth, pagination, or whether the list is fixed/cacheable, so it is not fully rich.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tightly packed sentences, front-loaded with what is listed and followed immediately by the return shape and sibling relationship. No filler or restatement of the tool name.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description supplies the return shape (STATEKEY/STATENAME pairs) and the consumer relationship to get_portal_statuses. Combined with a fully documented single parameter and a read-only annotation, an agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single PORTALSTATES enum is fully documented in the schema (partition scoping for TRUE vs omitted). The description adds no parameter detail, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource (lists the portal states NetBox defines) and pins down the return shape as STATEKEY/STATENAME pairs. It explicitly distinguishes itself from the sibling get_portal_statuses by framing itself as the vocabulary the latter reports against.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It names the sibling it relates to and the condition that makes it useful (to interpret or filter get_portal_statuses' STATEKEY). It stops short of an explicit when-not-to-use statement, but the routing intent is clear without opening either schema.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_portal_statusesARead-only
Returns the live status of portals (doors) — the current state of each door, not its configuration (wraps NBAPI GetPortalStatuses). Each PORTALSTATUS block carries PORTALKEY, PORTALNAME, STATEKEY, STATENAME, THREATLEVELNAME, LOCATIONKEY, LOCATIONNAME, TYPEKEY and PARTITIONKEY. Filter by portal, state, partition or location; use get_portal_states for the STATEKEY vocabulary and get_locations for LOCATIONKEY values.
| Name | Required | Description | Default |
|---|---|---|---|
| STATEKEY | No | Optional. Report only portals currently in this state (see get_portal_states). | |
| PORTALKEY | No | Optional. Report status for this portal only. | |
| LOCATIONKEY | No | Optional. Report status for portals at this location only (see get_locations). | |
| PARTITIONKEY | No | Optional. Report status for portals in this partition only. | |
| ALLPARTITIONS | No | Optional. "TRUE" to report portal status across all partitions. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so safety is covered; the description adds the genuinely behavioral trait that this is live/current state rather than stored configuration, and notes it wraps NBAPI GetPortalStatuses. It omits auth needs, pagination, and large-result behavior, so it is helpful but not complete.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and the config-vs-status distinction, followed by the filter summary and vocabulary routing. The PORTALSTATUS field enumeration is long but justified since no output schema exists; slightly dense overall.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, enumerating the returned PORTALSTATUS fields (PORTALKEY, STATEKEY, STATENAME, etc.) is exactly the missing context an agent needs, and the vocabulary cross-references close the loop on filter values. Only pagination/partition-default and auth expectations are absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and each parameter already documents itself and cross-references the vocabulary tools, so the schema does the heavy lifting. The description only summarizes 'filter by portal, state, partition or location' and never mentions ALLPARTITIONS, adding marginal meaning beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns the live status of portals') and explicitly carves out scope versus the sibling: 'the current state of each door, not its configuration'. An agent can distinguish this from get_portals without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete usage context ('Filter by portal, state, partition or location') and routes the agent to sibling tools for parameter vocabularies (get_portal_states for STATEKEY, get_locations for LOCATIONKEY). It does not state an explicit when-not or name get_portals as the configuration alternative, so it falls short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_readerBRead-only
Returns the details of a single reader for a given READERKEY (wraps NBAPI GetReader).
| Name | Required | Description | Default |
|---|---|---|---|
| READERKEY | Yes | Required. The unique READERKEY of the reader to retrieve. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true already tells the agent this is a safe read. The description adds only that it 'wraps NBAPI GetReader,' an implementation detail that does not help invocation, and says nothing about not-found/error behavior or what happens with an unknown key.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with the verb and resource up front. The parenthetical wrapper note is the only arguably non-earning clause, but the overall size is efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter read tool, the essentials are covered. With no output schema, however, the description does not indicate what 'details' are returned (credential format, location, status), leaving the return shape unspecified.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents READERKEY as required and unique. The description repeats the parameter name but adds no format, source, or lookup guidance beyond it, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clear verb (returns details) plus resource (a single reader) keyed by READERKEY. The word 'single' implicitly distinguishes it from the sibling get_readers, but the differentiation is left for the agent to infer rather than stated.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: 'a single reader' suggests this is the per-item companion to get_readers, but there is no explicit when-to-use or when-not-to-use statement, and no alternative is named.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_reader_access_historyARead-only
Returns a single reader's access (grant/deny) history for a given READERKEY, with each match's PERSONID enriched to a name (composite: wraps NBAPI GetAccessHistory + GetPerson). GetAccessHistory has no server-side reader filter, so this reads and filters client-side. Rather than a date range, it scans the most recent SCANWINDOW system-wide records (default 2000). RESOLVEDESCRIPTIONS defaults to true — an inverted, opt-out default like get_access_history's own RESOLVEDESCRIPTIONS: it attaches a single top-level READERDESCRIPTION field for the given READERKEY (not one per match — every match already shares this identical READERKEY by construction) via one GetReaders full-table fetch; set to false to omit it entirely.
| Name | Required | Description | Default |
|---|---|---|---|
| READERKEY | Yes | Required. Only access records for this reader are returned. | |
| MAXMATCHES | No | Optional. Maximum number of matches to include, in chronological order. Defaults to 100. | |
| SCANWINDOW | No | Optional. Number of most-recent system-wide access records to scan. Defaults to 2000. | |
| RESOLVEDESCRIPTIONS | No | Optional (default true — on by default). Attaches a single top-level READERDESCRIPTION field (the description for this call's own READERKEY, not one per match) via one GetReaders full-table fetch. Set to false to omit the field entirely. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With only readOnlyHint available, the description carries real weight: it discloses the client-side scan over system-wide records, the SCANWINDOW fallback instead of a date range, and the cost of the RESOLVEDESCRIPTIONS path (one GetReaders full-table fetch). These are genuine operational traits an agent could not infer from annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded and every claim is substantive, but the body is one dense run-on that re-explains RESOLVEDESCRIPTIONS details the schema already states verbatim, and the inverted-default caveat is buried mid-paragraph. It is informative but not tightly structured.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, and the description supplies the important return-shape nuance (PERSONID enriched to a name, single top-level READERDESCRIPTION). It does not describe ordering guarantees or what a match record otherwise contains, leaving a modest gap for a history-returning tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, but the description adds meaning beyond the schema: SCANWINDOW counts most-recent system-wide records rather than the reader's own, and READERDESCRIPTION is attached once at the top level rather than per match, with the reasoning. That is real semantic enrichment over the field text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource — 'Returns a single reader's access (grant/deny) history for a given READERKEY' — and explicitly frames itself as a composite wrapper over NBAPI GetAccessHistory + GetPerson. It is clearly distinguishable from the sibling get_access_history by scoping to one reader.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explains the key selection context: GetAccessHistory has no server-side reader filter, so this tool filters client-side, which tells an agent when this wrapper is the right call and hints at its cost. It references get_access_history as the sibling precedent but never states an explicit when-not-to-use rule or a preferred alternative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_reader_groupCRead-only
Returns the details of a single reader group for a given READERGROUPKEY (wraps NBAPI GetReaderGroup).
| Name | Required | Description | Default |
|---|---|---|---|
| READERGROUPKEY | Yes | Required. The unique READERGROUPKEY of the reader group to retrieve. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=true, so the safety profile is covered. The description adds only the 'wraps NBAPI GetReaderGroup' implementation note, which describes internals rather than behavior; it says nothing about error handling for a bogus key or what fields are returned.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One front-loaded sentence with the key constraint and identity clear. The redundant parenthetical is the only wasted content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple single-param read tool this is nearly adequate, but it omits any return-shape hint (no output schema), error behavior for invalid keys, and the sibling routing an agent needs to distinguish it from get_reader_groups.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the single parameter is documented there as required and unique. The description only echoes the parameter name, adding no format, casing, or sourcing guidance beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Returns) and resource (details of a single reader group) with the identifying key, distinguishing it from the sibling get_reader_groups (plural). The parenthetical wrapper note adds no purpose but doesn't obscure it.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No when-to-use guidance and no explicit routing to get_reader_groups for listing or get_reader for the hardware itself. The singular/plural sibling distinction is only implied by the word 'single'.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_reader_groupsCRead-only
Lists reader groups configured on the NetBox system (wraps NBAPI GetReaderGroups).
| Name | Required | Description | Default |
|---|---|---|---|
| STARTFROMKEY | No | Optional. Pagination cursor to continue listing from a previous call. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the safety profile is covered. The description adds only 'wraps NBAPI GetReaderGroups', an implementation detail rather than useful behavior; it does not mention pagination behavior even though a cursor parameter exists.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single efficient sentence that front-loads the verb and resource. The parenthetical wrapper note is marginally wasteful but brief, so it doesn't harm readability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only list tool with a fully documented single parameter and no output schema, the definition is adequate. It could be stronger by noting pagination intent or routing to the singular sibling, but nothing critical is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the single STARTFROMKEY parameter is already fully documented in the schema as a pagination cursor. The description adds no parameter meaning beyond that, which is the expected baseline when the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Lists') and resource ('reader groups') with a location qualifier ('configured on the NetBox system'), so the agent knows exactly what it retrieves. It doesn't explicitly differentiate itself from the singular sibling get_reader_group, but the plural scope is reasonably inferable.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this versus the nearby get_reader_group or get_readers siblings, and no prerequisites or exclusions are stated. Usage is only implied by the word 'Lists'.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_readersARead-only
Lists readers configured on the NetBox system (wraps NBAPI GetReaders). There is no portal-id filter — use get_portals to see each reader nested under its portal.
| Name | Required | Description | Default |
|---|---|---|---|
| STARTFROMKEY | No | Optional. Pagination cursor to continue listing from a previous call. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the safety profile is covered. The description adds only marginal context ('wraps NBAPI GetReaders') and the absence of a filter; it says nothing about result size, pagination behavior, or ordering beyond what the schema's cursor parameter implies.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, zero filler, and the scoping/limitation statement is front-loaded right after the purpose. Nothing extraneous.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only list tool with one optional parameter and no output schema, the description covers purpose, the key limitation, and the alternative route. Nothing essential for invocation is missing; only richer return-shape detail is absent, which is acceptable without an output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the sole parameter (STARTFROMKEY) is fully documented in the schema as a pagination cursor. The description adds only the negative statement that no portal-id filter parameter exists, which marginally clarifies the parameter surface but adds no syntax or format detail. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Lists readers') with explicit scope ('configured on the NetBox system'). It also differentiates from the sibling get_portals by clarifying that no portal-id filter exists, so the agent can tell the two apart without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly gives the alternative route: 'There is no portal-id filter — use get_portals to see each reader nested under its portal.' This directly tells the agent when to pick a different sibling, which is exactly the routing guidance this dimension rewards.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_sioARead-only
Returns the details of a single SIO for a given SIOKEY (wraps NBAPI GetSio). Note: the v2 guide's worked example for GetSio sends MERCURYKEY, but its Calling Parameters list — which the guide makes authoritative — specifies SIOKEY, so SIOKEY is what this tool sends.
| Name | Required | Description | Default |
|---|---|---|---|
| SIOKEY | Yes | Required. The key of the SIO to retrieve. Use get_sios to discover keys. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true already covers the safety profile, so the bar is lower, and the description clears it by disclosing that this wraps NBAPI GetSio and resolves a MERCURYKEY-vs-SIOKEY naming conflict in the underlying guide. That is real invocation-relevant context beyond the annotations, though it says nothing about response shape or error behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded in the first sentence and the total length is short. The follow-up "Note:" sentence about the guide's worked example versus its Calling Parameters list is somewhat inside-baseball, but it earns its place by preempting a plausible MERCURYKEY/SIOKEY mistake.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-param, read-only lookup with no output schema, the description covers purpose, the input key, and the naming caveat. It stops short of characterizing what "details" are returned, which is the only remaining gap for an agent deciding whether this tool gives it the fields it needs.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the param already documents itself as required with a discovery pointer, so the baseline is 3. The description goes further by explaining why SIOKEY (not MERCURYKEY) is the value actually transmitted, which is meaningfully beyond what the schema alone conveys.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Returns the details of a single SIO") plus the identifying input ("for a given SIOKEY"). The singular "single SIO" implicitly contrasts with the sibling get_sios, so an agent can route between them without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The singular-vs-plural framing implies this is the detail-lookup counterpart to get_sios, and the schema's own param text points to get_sios for key discovery. However, the description itself states no explicit when-to-use or when-not-to-use condition, leaving routing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_siosARead-only
Lists the SIOs (serial I/O boards) attached to one Mercury panel (wraps NBAPI GetSios). There is no unfiltered "all SIOs" listing — MERCURYKEY is required; the response carries a NEXTKEY pagination cursor.
| Name | Required | Description | Default |
|---|---|---|---|
| MERCURYKEY | Yes | Required. The Mercury panel key whose SIOs should be listed. Use get_mercury_panels to discover keys. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With readOnlyHint=true the safety profile is covered by annotations, and the description adds genuine behavioral context beyond that: the call wraps NBAPI GetSios and the response carries a NEXTKEY pagination cursor. It does not say how paging is continued or what fields an SIO record contains, but the pagination disclosure is meaningful.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the resource and scope, then the hard constraint and return-format note. Every clause carries information and nothing is padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-required-param read tool with no output schema, the description covers purpose, the mandatory input, and the pagination cursor an agent needs to handle results. What an SIO record actually contains is left unstated, which is a minor gap given no output schema exists.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the single MERCURYKEY parameter is fully documented in the schema, including how to discover keys via get_mercury_panels. The description only restates that MERCURYKEY is required, adding no new semantic detail — the baseline 3 is appropriate when the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb (lists) and resource (SIOs / serial I/O boards) with clear scope (attached to one Mercury panel), so an agent knows exactly what comes back. It does not explicitly differentiate itself from the sibling get_sio (singular), so it falls short of full sibling routing.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states a real usage constraint — there is no unfiltered "all SIOs" listing and MERCURYKEY is required — which tells the agent it must already hold a panel key before calling. It does not name an alternative tool or spell out prerequisites, so it stops short of explicit when/when-not guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_threat_levelsBRead-only
Lists threat levels configured on the NetBox system, optionally filtered (wraps NBAPI GetThreatLevels).
| Name | Required | Description | Default |
|---|---|---|---|
| ALLPARTITIONS | No | Optional. Per NBAPI GetThreatLevels filter. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true already tells the agent this is a safe read, and the description adds essentially nothing behavioral beyond that. 'Wraps NBAPI GetThreatLevels' is implementation trivia, not disclosure of return shape, pagination, or filter behavior, so the remaining behavioral burden is left unmet.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One compact sentence with the verb and resource front-loaded and no filler. The trailing parenthetical about wrapping the NBAPI call is the only content that does not earn its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
A simple read-only list tool with one optional parameter and no output schema needs little prose, so the definition is not badly incomplete. However, the meaning of the sole filter parameter and the shape of the returned list are left entirely unresolved, which is a real gap for an agent deciding how to call it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, but the single parameter is an opaque 'ALLPARTITIONS' whose schema text ('Optional. Per NBAPI GetThreatLevels filter.') explains nothing, and the description's 'optionally filtered' does not clarify it either. Baseline 3 applies since the schema nominally documents the parameter, but neither source resolves what value it accepts.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Lists threat levels') plus scope ('configured on the NetBox system'), which clearly separates it from sibling list tools such as get_partitions or get_access_levels. It does not name an alternative or explain how it relates to them, but the resource is distinct enough that no differentiation is strictly needed.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Optionally filtered' implies the tool is used to enumerate threat levels with an optional narrowing condition, which is the minimum viable usage hint. There is no statement of when to prefer this over siblings, no prerequisites, and no note about what the filter actually selects.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_time_specBRead-only
Returns the details of a single time spec for a given TIMESPECKEY (wraps NBAPI GetTimeSpec).
| Name | Required | Description | Default |
|---|---|---|---|
| TIMESPECKEY | Yes | Required. The unique TIMESPECKEY of the time spec to retrieve. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true already tells the agent this is a non-mutating read, and 'Returns the details' is consistent with that. The description adds no behavioral context beyond the annotation (no auth requirements, no not-found behavior, no pagination or cost notes), which is acceptable but thin given the low bar set by the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One tight sentence with the resource and key front-loaded; nothing is padded. The trailing '(wraps NBAPI GetTimeSpec)' is internal provenance that doesn't help selection, keeping it short of a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter read tool with no output schema, the definition covers the minimum: what it returns and what it needs. It omits the shape of the returned time spec and the relationship to get_time_specs/get_time_spec_group, which would materially help an agent choose correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so TIMESPECKEY is already fully documented (required, unique key). The description names the key but adds no format, origin, or lookup hints beyond what the schema states, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Returns) and resource (details of a single time spec) plus the identifying input (TIMESPECKEY). The word 'single' implicitly separates it from the plural sibling get_time_specs, but that sibling is never named, so differentiation must be inferred.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: fetch one time spec when you have its key. There is no explicit when-to-use guidance, no statement of when to prefer get_time_specs (list) over this, and no mention of error behavior for an unknown key.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_time_spec_groupARead-only
Returns the details of a single time spec group for a given TIMESPECGROUPKEY (wraps NBAPI GetTimeSpecGroup).
| Name | Required | Description | Default |
|---|---|---|---|
| TIMESPECGROUPKEY | Yes | Required. The unique TIMESPECGROUPKEY of the time spec group to retrieve. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already declares the safe read-only profile, so the bar is lower. The description adds that this is a single-item retrieval by key, but it does not disclose error behavior, return format, auth needs, or rate limits.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The definition is a single front-loaded sentence with no wasted words. The parenthetical about wrapping NBAPI GetTimeSpecGroup is minor implementation context and does not obscure the core action.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter read-only getter with full schema coverage and an annotation, the description is sufficient to invoke correctly. The remaining gap is that 'details' is vague and no return shape is described in the absence of an output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single parameter is fully documented in the schema. The description repeats TIMESPECGROUPKEY but adds no format, syntax, or semantic detail beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Returns'), resource ('details of a single time spec group'), and retrieval condition ('for a given TIMESPECGROUPKEY'). The word 'single' distinguishes it from the plural sibling get_time_spec_groups without needing to open either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use or when-not-to-use guidance, and no alternative tool is named. The only implied usage is that a TIMESPECGROUPKEY is required, which is already stated in the schema.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_time_spec_groupsARead-only
Lists time spec groups configured on the NetBox system (wraps NBAPI GetTimeSpecGroups). RESOLVEMEMBERNAMES defaults to true — an inverted, opt-out default (unlike most optional booleans in this codebase): GetTimeSpecGroups' TIMESPECKEYS.TIMESPECKEY member field carries only bare TIMESPECKEY strings, so this replaces each group member with a {TIMESPECKEY, NAME} object via one GetTimeSpecs full-table fetch per call (not per group/member). An unmatched (unknown/deleted) member key resolves to NAME: ''. Applies only to this plural tool, not the singular get_time_spec_group (confirmed broken/NOT FOUND on this controller — out of scope). Set RESOLVEMEMBERNAMES: false to skip the fetch and return TIMESPECKEYS exactly as GetTimeSpecGroups provides it (bare string or array of strings).
| Name | Required | Description | Default |
|---|---|---|---|
| STARTFROMKEY | No | Optional. Pagination cursor to continue listing from a previous call. | |
| RESOLVEMEMBERNAMES | No | Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Replaces each group's TIMESPECKEYS.TIMESPECKEY bare member key(s) with {TIMESPECKEY, NAME} objects via one GetTimeSpecs full-table fetch per call (not per group/member); an unmatched key resolves to NAME: ''. Set to false to skip the fetch and return TIMESPECKEYS exactly as GetTimeSpecGroups provides it (bare string or array of strings). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint, so the description carries real weight and delivers: the inverted opt-out default, the one full-table GetTimeSpecs fetch per call (not per group), the unmatched-key-to-empty-string behavior, and the exact return shape with the flag off. These are non-obvious costs and edge cases an agent could not infer elsewhere.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded and the sentences are dense with substance rather than filler. There is mild duplication since the RESOLVEMEMBERNAMES explanation is restated in the schema, and the parentheticals stack up, but nothing is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description fills the gap by describing the returned TIMESPECKEYS shape in both modes, plus the pagination cursor semantics via STARTFROMKEY. Nothing needed to invoke or interpret this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the RESOLVEMEMBERNAMES schema text is essentially identical to the description text, so the description adds no parameter meaning beyond the schema. The only extra is the scope note that the behavior applies to this plural tool only. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Lists time spec groups configured on the NetBox system') and even names the underlying API call. It explicitly distinguishes itself from the singular sibling get_time_spec_group and from get_time_specs, so an agent can separate it from neighbors without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear operational guidance: set RESOLVEMEMBERNAMES to false to skip the fetch, and the note that the singular get_time_spec_group is broken and out of scope steers the agent away from a dead end. It stops short of an explicit when-to-use-this-vs-get_time_specs rule, but the context is unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_time_specsBRead-only
Lists time specs configured on the NetBox system (wraps NBAPI GetTimeSpecs).
| Name | Required | Description | Default |
|---|---|---|---|
| STARTFROMKEY | No | Optional. Pagination cursor to continue listing from a previous call. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint=true annotation already carries the safety profile, so the description is not required to explain that this is a read. It adds essentially nothing behavioral beyond restating the wrapping implementation detail—no pagination behavior, result ordering, or size limits are described despite the cursor parameter hinting at paging.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with the purpose stated first. It is efficiently sized, though the '(wraps NBAPI GetTimeSpecs)' clause adds little for an agent and could be dropped.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only list tool with one optional cursor parameter, no output schema, and annotations covering safety, the description is broadly adequate. The main gap is the absence of any distinction from the closely named singular/group siblings.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single STARTFROMKEY cursor parameter is fully documented in the schema itself. The description adds no further meaning about the parameter, so the baseline 3 for schema-covered parameters is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Lists') and resource ('time specs') with scope ('configured on the NetBox system'), so the agent can identify it as an enumeration tool. However, it does not differentiate from the singular sibling get_time_spec or the related get_time_spec_group(s), leaving ambiguity about which to pick.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied by the plural 'Lists' (retrieve all time specs). There is no explicit when-to-use versus get_time_spec (single-item) or get_time_spec_groups, and no prerequisites or exclusions are stated. The '(wraps NBAPI GetTimeSpecs)' note is an implementation detail, not usage guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_udf_list_itemsBRead-only
Returns the items of a single UDF list for a given UDFLISTKEY (wraps NBAPI GetUDFListItems).
| Name | Required | Description | Default |
|---|---|---|---|
| UDFLISTKEY | Yes | Required. The unique UDFLISTKEY of the UDF list to retrieve. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint=true annotation already establishes this is a safe read, so the bar is lower; the description adds the useful note that it wraps NBAPI GetUDFListItems. It does not disclose anything about the returned item structure, pagination, or error behavior for an invalid key.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One compact sentence that leads with the action and resource, with the wrapped-API note as a parenthetical. Nothing is wasted, though the sentence is thin on the information an agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter read with readOnlyHint and full schema coverage, the description is adequate but not complete: it never clarifies how it relates to get_udf_lists, and with no output schema it says nothing about what an 'item' contains.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% for the single UDFLISTKEY parameter, so the schema already carries the semantics. The description restates that the key selects a single list but adds no format or syntax detail beyond the schema, which is the expected baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource: returning the items of a single UDF list keyed by UDFLISTKEY, and names the upstream API it wraps. However, it never distinguishes itself from the sibling get_udf_lists, which an agent could easily confuse with this one.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use statement, no mention of when not to use it, and no reference to the sibling get_udf_lists as the alternative for enumerating lists. Only the phrase 'for a given UDFLISTKEY' hints at the required prerequisite.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_udf_listsBRead-only
Lists user-defined field (UDF) lists configured on the NetBox system (wraps NBAPI GetUDFLists). No parameters required.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the safety profile is covered. Beyond that, the description adds nothing behavioral: it does not say whether all lists are returned, whether results are paginated, or what a UDF list contains. The 'wraps NBAPI GetUDFLists' note is an implementation detail with no decision value.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with the resource named first. The parenthetical wrapper reference is the only expendable fragment, but overall it is tight.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
A no-arg, read-only list tool is a low-complexity case; annotations cover the safety profile and there is no output schema to explain. The description is adequate, though a one-line note on the get_udf_lists vs get_udf_list_items relationship would close the remaining gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, which is the baseline-4 case. The description's 'No parameters required' correctly confirms this and matches the empty schema, with no misleading parameter hints.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Lists) and resource (user-defined field lists) and even expands the UDF acronym, which is genuinely useful. It does not, however, distinguish itself from the closely named sibling get_udf_list_items (the items within a list), so an agent must infer the boundary from naming alone.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use statement, no prerequisites, and no mention of the obvious alternative get_udf_list_items. 'No parameters required' describes the call mechanics rather than when an agent should reach for this tool over its sibling.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_unlock_windowARead-only
Reports the managed unlock window, if any: the managed portal group "MCP Unlock Window" (key, portals, its unlock time spec group and whether that is the managed time spec group "MCP Unlock Window time specs"), that group and its members (read from paginated GetTimeSpecGroups), the managed time specs and holidays, the window derived from them, and whether it is active right now on the host clock. Read-only: issues only Get commands. No parameters required.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already supply readOnlyHint=true, so the description's 'Read-only: issues only Get commands' is largely a restatement. It does add genuine context beyond annotations, though: the data sources (paginated GetTimeSpecGroups, managed time specs and holidays), the derivation chain, and evaluation against the host clock. These are useful behavioral details.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded, but the body is a single run-on sentence dense with nested parentheticals and repeated group names, making it hard to parse. Roughly the right size, but structurally strained.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the burden of describing returns and does so in detail: the portal group, its members, time specs/holidays, the derived window, and the active-now boolean. Only the sibling differentiation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Parameter count is zero and schema coverage is 100%, so the baseline is 4. The statement 'No parameters required' adds nothing beyond an empty schema, but no penalty is warranted for a parameterless tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb ('Reports') and resource ('the managed unlock window') and elaborates the exact contents returned. However, it never distinguishes itself from the closely-named sibling get_daily_unlock_window, so an agent cannot tell the two apart from the text alone.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use guidance and no mention of alternatives. The only contextual note is 'No parameters required,' which is a schema fact rather than usage direction. With a near-twin sibling 'get_daily_unlock_window' present, the absence of any routing guidance is a real gap.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_virtual_credential_requestARead-only
Retrieves a mobile (virtual) credential and its assignment state for a person and card format (wraps NBAPI GetVirtualCredentialRequest). Returns CARDFORMAT, PERSONID and STATUS. A card format the controller does not know answers FAIL with "CARDFORMAT NOT FOUND"; use get_card_formats for valid names.
| Name | Required | Description | Default |
|---|---|---|---|
| PERSONID | Yes | Required. The PERSONID the credential is assigned to. | |
| CARDFORMAT | Yes | Required. Name of the card format used to decode the credential (see get_card_formats). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations supply readOnlyHint=true, so safety is covered; the description still adds real behavior by disclosing the FAIL/"CARDFORMAT NOT FOUND" error semantics and the backing NBAPI call. It does not mention auth requirements or result cardinality, keeping it short of a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, front-loaded with the core purpose before the return fields and failure note. The parenthetical implementation reference is a minor cost but earns its place for traceability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 2-param read tool with no output schema, the description covers purpose, inputs, return fields, and the key failure mode, so nothing essential is missing. Rate limits, pagination, or auth context are absent but not critical here.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% with both params described, so baseline is 3. The description goes beyond the schema by explaining the consequence of an invalid CARDFORMAT (FAIL with 'CARDFORMAT NOT FOUND') and pointing at get_card_formats, adding meaning the schema does not carry.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Retrieves) and resource (mobile/virtual credential and its assignment state) with scope qualifying it (for a person and card format). It also names the return fields, letting an agent distinguish it from generic person/card lookups among the ~45 siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes to get_card_formats for valid CARDFORMAT names and explains the failure path when the controller does not know the format. It lacks a broader when-to-use/when-not statement versus other credential tools, but the parameter-sourcing guidance is concrete.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_eventsARead-only
Lists the event types/definitions known to the NetBox system (wraps NBAPI ListEvents). RESOLVEPARTITIONNAMES defaults to true — on by default, the same inverted opt-out default used by this codebase's other fixed-cost enrichments: each returned event is enriched with a PARTITIONNAME field resolved from its PARTITIONID via one GetPartitions fetch per call, not per event (GetPartitions costs the same whether it resolves one event or a thousand). Set RESOLVEPARTITIONNAMES: false to skip it.
| Name | Required | Description | Default |
|---|---|---|---|
| RESOLVEPARTITIONNAMES | No | Optional (default true — on by default; an opt-out, not opt-in, default). Enriches each returned event with PARTITIONNAME resolved from its PARTITIONID via one GetPartitions fetch per call (not per event). Set to false to skip it and get the plain ListEvents response. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint annotation, the description adds genuinely useful operational context: it wraps NBAPI ListEvents and the enrichment costs exactly one GetPartitions fetch per call regardless of event count. That per-call cost model is real behavioral information an agent cannot get from annotations or schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded well, but the body is repetitive: 'defaults to true — on by default', 'inverted opt-out default', and 'not per event' are each restated, and the GetPartitions cost point is made twice. The content is correct but padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-optional-parameter read-only list tool with no output schema, the description covers purpose, the enrichment default and its cost, and how to opt out. It is close to complete, missing only what the plain ListEvents response actually contains.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the sole parameter is already fully documented, and the description's explanation of RESOLVEPARTITIONNAMES largely restates the schema text. The added 'not per event' cost framing is helpful but does not meaningfully extend semantics beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence names a specific verb and resource ('Lists the event types/definitions known to the NetBox system') and identifies the underlying API call (NBAPI ListEvents). It does not distinguish itself from nearby siblings like get_event_history or get_partitions, so the agent must infer which listing tool it actually wants.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear guidance on the RESOLVEPARTITIONNAMES parameter (when to set it false to skip enrichment), which is effectively usage instruction for the one option. However, it offers no tool-selection guidance — nothing says when to call list_events versus get_event_history or get_partitions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ping_appARead-only
Pings the NetBox NBAPI application to confirm it is responsive (wraps NBAPI PingApp). No parameters required.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the agent knows this is a safe, non-mutating probe. The description adds useful framing (it is a liveness check that wraps PingApp), but says nothing about expected output, timeout behavior, or what a failure looks like. With annotations carrying the safety profile, a 3 is appropriate.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single, front-loaded sentence with no filler; the purpose leads and the parenthetical wrapper detail is secondary. Nothing is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter, read-only health probe whose safety is covered by annotations, the description is nearly sufficient. The only minor omission is what a successful or failed ping returns, which would help an agent interpret the result since no output schema exists.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are zero parameters, so the baseline is 4. The explicit 'No parameters required' sentence confirms the caller needs to supply nothing, though it is largely redundant against an empty schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (pings), a specific resource (the NetBox NBAPI application), and the outcome it verifies (responsiveness), plus the underlying wrapper (NBAPI PingApp). This is clearly distinguishable from the many get_* data-retrieval siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives no guidance on when to reach for this tool versus alternatives such as the sibling check_connection, nor does it state any preconditions. It only notes that no parameters are required, which is not usage guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_person_dataBRead-only
Searches for person records matching the given criteria (wraps NBAPI SearchPersonData). Every documented SearchPersonData filter field is modeled explicitly; omit all filters to return every record.
| Name | Required | Description | Default |
|---|---|---|---|
| UDF1 | No | Optional. Search filter on user-defined field UDF1. | |
| UDF2 | No | Optional. Search filter on user-defined field UDF2. | |
| UDF3 | No | Optional. Search filter on user-defined field UDF3. | |
| UDF4 | No | Optional. Search filter on user-defined field UDF4. | |
| UDF5 | No | Optional. Search filter on user-defined field UDF5. | |
| UDF6 | No | Optional. Search filter on user-defined field UDF6. | |
| UDF7 | No | Optional. Search filter on user-defined field UDF7. | |
| UDF8 | No | Optional. Search filter on user-defined field UDF8. | |
| UDF9 | No | Optional. Search filter on user-defined field UDF9. | |
| NOTES | No | Optional. Match on the person record's notes text. | |
| UDF10 | No | Optional. Search filter on user-defined field UDF10. | |
| UDF11 | No | Optional. Search filter on user-defined field UDF11. | |
| UDF12 | No | Optional. Search filter on user-defined field UDF12. | |
| UDF13 | No | Optional. Search filter on user-defined field UDF13. | |
| UDF14 | No | Optional. Search filter on user-defined field UDF14. | |
| UDF15 | No | Optional. Search filter on user-defined field UDF15. | |
| UDF16 | No | Optional. Search filter on user-defined field UDF16. | |
| UDF17 | No | Optional. Search filter on user-defined field UDF17. | |
| UDF18 | No | Optional. Search filter on user-defined field UDF18. | |
| UDF19 | No | Optional. Search filter on user-defined field UDF19. | |
| UDF20 | No | Optional. Search filter on user-defined field UDF20. | |
| DELETED | No | Optional. Include/exclude deleted person records. | |
| HOTSTAMP | No | Optional. Match on a card hot-stamp number. | |
| LASTNAME | No | Optional. Match on the person's last name. | |
| PERSONID | No | Optional. Match on a specific PERSONID. | |
| FIRSTNAME | No | Optional. Match on the person's first name. | |
| CARDFORMAT | No | Optional. Match persons holding a card in this card format. | |
| CARDSTATUS | No | Optional. Match persons holding a card with this card status name. | |
| MIDDLENAME | No | Optional. Match on the person's middle name. | |
| MSUENABLED | No | Optional. Match on whether MSU mobile credentials are enabled ("TRUE"/"FALSE"). | |
| ACCESSLEVEL | No | Optional. Match persons assigned this access level. | |
| MOBILEPHONE | No | Optional. Match on the person's mobile phone number. | |
| CONTACTEMAIL | No | Optional. Match on the person's office email address. | |
| ALLPARTITIONS | No | Optional. Search across all partitions. | |
| NEWESTLASTMOD | No | Optional. Only include records last modified on/before this date/time. | |
| OLDESTLASTMOD | No | Optional. Only include records last modified on/after this date/time. | |
| RAWCARDNUMBER | No | Optional. Match on a raw (unformatted) card number. | |
| VEHICLELICNUM | No | Optional. Match on a vehicle license plate number. | |
| VEHICLETAGNUM | No | Optional. Match on a vehicle tag number. | |
| WILDCARDSEARCH | No | Optional. Treat text filters as wildcard patterns. | |
| CASEINSENSITIVE | No | Optional. Perform a case-insensitive match. | |
| WANTCREDENTIALID | No | Optional. Include CREDENTIALID values on returned access cards. | |
| ACCESSLEVELDETAILS | No | Optional. Include full access level details in the response. | |
| BLUEDIAMONDENABLED | No | Optional. Match on whether BlueDiamond mobile credentials are enabled ("TRUE"/"FALSE"). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the safety profile is covered. The description adds that it wraps NBAPI SearchPersonData and the omit-all-filters behavior, but says nothing about result volume, pagination, or how multiple filters combine (AND vs OR).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the core purpose and no filler. The parenthetical wrapper name is minor but not wasteful, and the empty-filter behavior is stated compactly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 44-parameter search tool with no output schema, the description is thin: it does not explain filter combination semantics, matching behavior (wildcard/case sensitivity toggles exist), or result shape, all of which an agent must call this correctly would benefit from.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all 44 filter fields. The description's claim that 'every documented SearchPersonData filter field is modeled explicitly' adds structural reassurance but no per-parameter meaning beyond the schema, matching the baseline 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Searches for person records matching the given criteria'), which is clear and actionable. However, it never distinguishes itself from the sibling get_person, which fetches a specific person record, leaving the boundary between the two to inference.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'omit all filters to return every record' gives implied usage for the empty case, but there is no explicit when-to-use vs get_person for single-record retrieval, and no exclusions or prerequisites.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
13 tool updates
v0.4.0- Added
get_alarms - Added
get_guide - Added
get_locations - Added
get_mercury_panel - Added
get_mercury_panels - Added
get_network_node - Added
get_network_nodes - Added
get_picture - Added
get_portal_states - Added
get_portal_statuses - Added
get_sio - Added
get_sios - Added
get_virtual_credential_request
14 tool updates
v0.3.0- Changed
get_access_history4 fields changed- removed
Input schema / properties / NEWESTDTTMRemoved value: -{ - "description": "Optional. Newest date/time to include.", - "type": "string" -} - removed
Input schema / properties / OLDESTDTTMRemoved value: -{ - "description": "Optional. Oldest date/time to include.", - "type": "string" -} - added
Input schema / properties / RESOLVEDESCRIPTIONSAdded value: +{ + "description": "Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Enrich each returned record with the reader's human-readable READERDESCRIPTION via one GetReaders full-table fetch per call (not per record). Set to false to skip it.", + "type": "boolean" +} - added
Input schema / properties / RESOLVENAMESAdded value: +{ + "description": "Optional (default false). Enrich each returned record with the badge-holder's FIRSTNAME/LASTNAME/FULLNAME/NOTES via one extra GetPerson call per distinct person found in the result.", + "type": "boolean" +}
- Changed
get_access_level1 field changed- added
Input schema / properties / RESOLVEGROUPNAMESAdded value: +{ + "description": "Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Resolves TIMESPECGROUPKEY/READERGROUPKEY into new TIMESPECGROUPNAME/READERGROUPNAME sibling fields via one full-table GetTimeSpecGroups fetch and one full-table GetReaderGroups fetch per call (each made only when that axis's key is non-empty; an empty/absent key on one axis yields '' for that axis's name without affecting the other). THREATLEVELGROUPKEY is never resolved — no NBAPI read command exists for threat level groups. Set to false to skip both fetches and return the response exactly as GetAccessLevel provides it.", + "type": "boolean" +}
- Changed
get_access_level_groups1 field changed- added
Input schema / properties / PARTITIONKEYAdded value: +{ + "description": "Optional. Per NBAPI GetAccessLevelGroups — only \"0\" is documented as allowed.", + "type": "string" +}
- Changed
get_access_levels1 field changed- added
Input schema / properties / PARTITIONKEYAdded value: +{ + "description": "Optional. Per NBAPI GetAccessLevels — only \"0\" is documented as allowed.", + "type": "string" +}
- Changed
get_card_access_details2 fields changed- added
Input schema / properties / RESOLVEDESCRIPTIONSAdded value: +{ + "description": "Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Enrich each returned ACCESS record with the reader's human-readable READERDESCRIPTION via one GetReaders full-table fetch per call (not per record). Set to false to skip it.", + "type": "boolean" +} - added
Input schema / properties / RESOLVENAMESAdded value: +{ + "description": "Optional (default false). Enrich the top level of the response with the card owner's FIRSTNAME/LASTNAME/FULLNAME/NOTES via a single GetPerson lookup for the response's top-level PERSONID (one lookup per call, not one per ACCESS record — a card has exactly one owner).", + "type": "boolean" +}
- Changed
get_holidays2 fields changed- removed
Input schema / additionalPropertiesRemoved value: -false - removed
Input schema / properties / STARTFROMKEYRemoved value: -{ - "description": "Optional. Pagination cursor to continue listing from a previous call.", - "type": "string" -}
- Changed
get_portal_group1 field changed- added
Input schema / properties / RESOLVEGROUPNAMESAdded value: +{ + "description": "Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Resolves UNLOCKTIMESPECGROUPKEY into a new UNLOCKTIMESPECGROUPNAME sibling field via one full-table GetTimeSpecGroups fetch per call (skipped when UNLOCKTIMESPECGROUPKEY is empty/absent, yielding '' for the name). THREATLEVELGROUPKEY is never resolved — no NBAPI read command exists for threat level groups. Set to false to skip the fetch and return the response exactly as GetPortalGroup provides it.", + "type": "boolean" +}
- Changed
get_portal_groups1 field changed- added
Input schema / properties / RESOLVEGROUPNAMESAdded value: +{ + "description": "Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Resolves each group's UNLOCKTIMESPECGROUPKEY bare foreign key into a new sibling UNLOCKTIMESPECGROUPNAME field, via at most one full-table GetTimeSpecGroups fetch per call (not per group) — built once and skipped entirely when every group on the page has an empty/absent UNLOCKTIMESPECGROUPKEY, yielding '' for the name. THREATLEVELGROUPKEY is never resolved — no NBAPI read command exists for threat level groups. Set to false to skip the fetch and return groups exactly as GetPortalGroups provides them.", + "type": "boolean" +}
- Changed
get_portals1 field changed- added
Input schema / properties / RESOLVEDESCRIPTIONSAdded value: +{ + "description": "Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Fills in each nested reader's own DESCRIPTION field (GetPortals leaves it unpopulated) via one GetReaders full-table fetch per call (not per portal/reader). Set to false to skip it and return readers exactly as GetPortals provides them.", + "type": "boolean" +}
- Added
get_reader_access_history - Added
get_threat_levels - Changed
get_time_spec_groups1 field changed- added
Input schema / properties / RESOLVEMEMBERNAMESAdded value: +{ + "description": "Optional (default true — on by default; the inverse of this codebase's usual optional-boolean default). Replaces each group's TIMESPECKEYS.TIMESPECKEY bare member key(s) with {TIMESPECKEY, NAME} objects via one GetTimeSpecs full-table fetch per call (not per group/member); an unmatched key resolves to NAME: ''. Set to false to skip the fetch and return TIMESPECKEYS exactly as GetTimeSpecGroups provides it (bare string or array of strings).", + "type": "boolean" +}
- Changed
list_events2 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / RESOLVEPARTITIONNAMESAdded value: +{ + "description": "Optional (default true — on by default; an opt-out, not opt-in, default). Enriches each returned event with PARTITIONNAME resolved from its PARTITIONID via one GetPartitions fetch per call (not per event). Set to false to skip it and get the plain ListEvents response.", + "type": "boolean" +}
- Changed
search_person_data9 fields changed- added
Input schema / properties / BLUEDIAMONDENABLEDAdded value: +{ + "description": "Optional. Match on whether BlueDiamond mobile credentials are enabled (\"TRUE\"/\"FALSE\").", + "type": "string" +} - added
Input schema / properties / CARDFORMATAdded value: +{ + "description": "Optional. Match persons holding a card in this card format.", + "type": "string" +} - added
Input schema / properties / CARDSTATUSAdded value: +{ + "description": "Optional. Match persons holding a card with this card status name.", + "type": "string" +} - added
Input schema / properties / CONTACTEMAILAdded value: +{ + "description": "Optional. Match on the person's office email address.", + "type": "string" +} - added
Input schema / properties / MOBILEPHONEAdded value: +{ + "description": "Optional. Match on the person's mobile phone number.", + "type": "string" +} - added
Input schema / properties / MSUENABLEDAdded value: +{ + "description": "Optional. Match on whether MSU mobile credentials are enabled (\"TRUE\"/\"FALSE\").", + "type": "string" +} - added
Input schema / properties / NOTESAdded value: +{ + "description": "Optional. Match on the person record's notes text.", + "type": "string" +} - added
Input schema / properties / VEHICLELICNUMAdded value: +{ + "description": "Optional. Match on a vehicle license plate number.", + "type": "string" +} - added
Input schema / properties / VEHICLETAGNUMAdded value: +{ + "description": "Optional. Match on a vehicle tag number.", + "type": "string" +}
36 tool updates
v0.2.3- First observed
check_connection - First observed
find_portals - First observed
get_access_history - First observed
get_access_level - First observed
get_access_level_group - First observed
get_access_level_groups - First observed
get_access_level_names - First observed
get_access_levels - First observed
get_card_access_details - First observed
get_card_formats - First observed
get_daily_unlock_window - First observed
get_elevators - First observed
get_event_history - First observed
get_floors - First observed
get_holiday - First observed
get_holidays - First observed
get_outputs - First observed
get_partitions - First observed
get_person - First observed
get_portal_group - First observed
get_portal_groups - First observed
get_portals - First observed
get_reader - First observed
get_reader_group - First observed
get_reader_groups - First observed
get_readers - First observed
get_time_spec - First observed
get_time_spec_group - First observed
get_time_spec_groups - First observed
get_time_specs - First observed
get_udf_list_items - First observed
get_udf_lists - First observed
get_unlock_window - First observed
list_events - First observed
ping_app - First observed
search_person_data
TDQS
Scored across 51 tools
Tools are mostly distinct by resource and action, and singular/plural pairs follow a predictable pattern. Portal-related tools (get_portals, find_portals, get_portal_statuses, get_portal_groups) and history tools could momentarily overlap, but the detailed descriptions resolve boundaries clearly.
Naming is predominantly a consistent snake_case get_* pattern, with minor deviations like list_events, search_person_data, find_portals, check_connection, and ping_app. These deviations are readable and clearly signal the tool's special behavior.
At 51 tools, the surface far exceeds the typical 3-15 well-scoped range and crosses the rubric's 50+ extreme-mismatch threshold. Even though each tool maps to a distinct API endpoint, the sheer count imposes a heavy selection burden on agents.
The toolset is entirely read-only, with no create, update, or delete operations for the access-control domain. Read coverage is broad, but the absence of lifecycle write operations (e.g., setting unlock windows, granting access) is a significant gap that will block management tasks.
Maintenance
Related MCP Connectors
An MCP server that provides an API to LLMs to manage their JumpCloud resources.
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
One OAuth connector for your team's remote MCP servers, with Tool Packs, guardrails and logs.
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceExposes the UniFi Network Integration API as MCP tools, dynamically loaded from JSON manifests, with read-only mode by default.MIT
- AlicenseAqualityAmaintenanceExposes N-able N-central REST API as MCP tools for managing devices, organizations, users, and more, with support for read-only, write, and full write modes.125MIT
- AlicenseAqualityAmaintenanceProvides MCP tools for governed multi-vendor network device operations, including configuration management (backup, diff, merge, replace, rollback) and read-only queries (facts, interfaces, BGP, LLDP, ARP) via NAPALM, with optional NetBox source-of-truth integration.33MIT
- AlicenseNot gradedqualityCmaintenanceEnables MCP clients to connect to Authentik and securely query users, groups, applications, events, and system status, with an optional gated tool to enable or disable user accounts.MIT