Skip to main content
Glama
J-MaFf

s2-netbox-mcp

by J-MaFf

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 — see docs/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.md records the result, including which documented commands are deliberately not implemented and why.

WARNING

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/npm are on your PATH:

    winget install OpenJS.NodeJS.LTS
  • An 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-mcp

This 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 build

Either 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 .env

Start 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 start

Registering 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

.mcp.json in your project, or claude_desktop_config.json

Or add non-interactively via claude mcp add-json s2-netbox-mcp '<json>'

Antigravity

~/.gemini/config/mcp_config.json (Windows: %USERPROFILE%\.gemini\config\mcp_config.json)

Global — applies to every Antigravity session

Gemini CLI

~/.gemini/settings.json, under its own mcpServers key

Or add non-interactively via gemini mcp add s2-netbox-mcp <command> [args] -e KEY=value

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-mcp

The 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). Login still returns SUCCESS with a session ID, but every subsequent command — including Logout — fails with APIERROR 5. Fix: tick that checkbox on the Data Integration tab. This server's client detects this exact pattern (a successful re-login followed by another APIERROR 5) and surfaces a tool error naming the checkbox directly.

  • "HTTP 410 Gone." The configured NETBOX_API_PATH is 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/nbapi path is deregistered on 6.x and returns 410 for every request. If you're on a pre-6.x controller, set NETBOX_API_PATH=/goforms/nbapi explicitly; if you're on 6.x and still see this, double-check NETBOX_API_PATH isn't set to something else by mistake.

Environment variables

Variable

Required

Default

Description

NETBOX_BASE_URL

Yes

—

Base URL of the NetBox controller's web interface, e.g. https://netbox.example.internal. No trailing slash or path — the client appends NETBOX_API_PATH itself.

NETBOX_USERNAME

Yes

—

NBAPI session-login username.

NETBOX_PASSWORD

Yes

—

NBAPI session-login password. Never logged, never written to any tracked file.

NETBOX_ALLOW_INSECURE_TLS

No

false

Set to true/1/yes to accept a self-signed/on-prem TLS certificate. Explicit opt-in only — any other value (including unset) keeps normal certificate verification.

NETBOX_API_PATH

No

/nbws/goforms/nbapi

The NBAPI path appended to NETBOX_BASE_URL. The default is the verified path on NetBox 6.x controllers. Only set this to override the default — e.g. to the legacy, pre-6.x path /goforms/nbapi, which returns HTTP 410 Gone on 6.x controllers (see Controller prerequisites below). A value without a leading / has one added automatically.

NETBOX_ENABLE_WRITES

No

false

Set to true/1/yes to register the write tools (see Write access below). Unset (or any other value) leaves the server strictly read-only.

NETBOX_ENABLE_DESTRUCTIVE

No

false

Set to true/1/yes, together with NETBOX_ENABLE_WRITES, to additionally register the 11 destructive tools (see Write access below).

NETBOX_EVENT_API_PATH

No

tracks NETBOX_API_PATH

Request path used only for trigger_event. Unset/empty tracks whatever NETBOX_API_PATH resolves to; a non-empty override is used verbatim (leading / added if missing) — e.g. the doc's pre-6.x Event API path /appd/nbapi, if your controller serves it separately.

NETBOX_UNLOCK_HOLIDAY_GROUPS

No

8,7,6

The holiday groups reserved for the managed unlock window, in first,middle,last segment order — see Scheduled unlock windows below. Must be 1-3 distinct integers in 1..8, comma-separated; reserve groups nothing else on the controller uses.

NETBOX_UNLOCK_NAME_PREFIX

No

MCP Unlock Window

Name prefix of every object the managed unlock window creates: the portal group (<prefix>), the time spec group (<prefix> time specs), and the per-segment holidays/time specs (<prefix> first/middle/last). 1-40 characters so the longest name (<prefix> time specs) fits the 64-character NAME limit.

NETBOX_DAILY_UNLOCK_HOLIDAY_GROUP

No

5

The single holiday group reserved for the managed daily recurring unlock window — see Scheduled daily unlock windows below. Must be a single integer in 1..8, and must not be a member of NETBOX_UNLOCK_HOLIDAY_GROUPS (the two features' reserved groups can never collide).

NETBOX_DAILY_UNLOCK_NAME_PREFIX

No

MCP Daily Unlock Window

Name prefix of every object the managed daily unlock window creates: the portal group (<prefix>), the time spec group (<prefix> time specs), and the one holiday/time spec (<prefix> schedule). 1-40 characters so the longest name fits the 64-character NAME limit.

NETBOX_LIVE_TEST_PORTALKEY

No

—

The PORTALKEY of the one door you designate safe to physically unlock during npm run test:live:write/npm run test:live:write:daily. Read only by those scripts, never by the server itself.

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:

  • Login

  • Logout

  • GetAPIVersion

  • GetPerson

  • SearchPersonData

  • GetCardAccessDetails

  • GetCardFormats

  • GetAccessLevel(s)

  • GetAccessLevelGroup(s)

  • GetPortals

  • GetReader(s)

  • GetOutputs

  • GetTimeSpec(s)

  • GetTimeSpecGroup(s)

  • GetHoliday(s)

  • GetPortalGroup(s)

  • GetReaderGroup(s)

  • GetAccessLevelNames

  • GetPartitions

  • GetUDFLists

  • GetUDFListItems

  • GetElevators

  • GetFloors

  • PingApp

  • GetEventHistory

  • ListEvents

  • GetAccessHistory

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=true registers 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 tools set_portals_state, schedule_unlock_window, and cancel_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 without NETBOX_ENABLE_DESTRUCTIVE because those objects are server-owned; they never delete anything else.

  • NETBOX_ENABLE_DESTRUCTIVE=true, set in addition to NETBOX_ENABLE_WRITES, registers the 11 destructive tools (each description is DESTRUCTIVE:-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_person refuses a call with DELETED="TRUE" or PERSONPURGE="TRUE", and modify_udf_list_items refuses a call where any list item has DELETE="1" — both name NETBOX_ENABLE_DESTRUCTIVE in the error and send nothing to the controller.

  • Every write tool's description starts with WRITE: (or DESTRUCTIVE: for the 11 above), and every successful write's result text contains the literal SUCCESS followed 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 READERKEY or READERGROUPKEY, 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

access-model

The person → credential → access level → access level group chain, and the portal-group/time-spec-group name-table collision.

unlock-windows

The holiday + time spec + portal group UNLOCKTIMESPECGROUPKEY recipe, date/time inclusivity rules, and when to prefer the composite tools.

group-and-name-gotchas

modify_portal_group/modify_reader_group replacing a group's entire membership; modify_access_level always needing TIMESPECGROUPKEY.

credentials-and-card-formats

Diagnosing a BIT MISMATCH access-denied event, and remove_person's soft-delete behavior.

api-quirks

STARTFROMKEY/NEXTKEY paging, the missing singular get_portal, and checking write results for the literal SUCCESS.

write-safety

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?

Onboard

Checks for a duplicate person, creates the person with access levels (block syntax), issues a card, then verifies.

Yes

Offboard

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

Event unlock

Finds the doors with find_portals, checks any existing managed window, does a dry run, then calls schedule_unlock_window / schedule_daily_unlock_window. Undo is the matching cancel tool.

Yes

Badge denied

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

check_connection

GetAPIVersion

—

get_guide

— (pure in-process lookup; no controller call)

— (optional topic)

get_person

GetPerson

PERSONID

search_person_data

SearchPersonData

— (all filters optional)

get_card_access_details

GetCardAccessDetails

ENCODEDNUM, CARDFORMAT (optional MAXRECORDS/OLDESTDTTM/RESOLVENAMES/RESOLVEDESCRIPTIONS)

get_card_formats

GetCardFormats

—

get_access_level

GetAccessLevel

ACCESSLEVELKEY (optional RESOLVEGROUPNAMES)

get_access_levels

GetAccessLevels

— (optional PARTITIONKEY/STARTFROMKEY/STARTFROMNAME/WANTKEY)

get_access_level_group

GetAccessLevelGroup

ACCESSLEVELGROUPKEY

get_access_level_groups

GetAccessLevelGroups

— (optional STARTFROMKEY/PARTITIONKEY)

get_access_level_names

GetAccessLevelNames

— (optional PARTITIONKEY/STARTFROMNAME)

get_portals

GetPortals

— (optional STARTFROMKEY/RESOLVEDESCRIPTIONS; no single-portal filter — returns each portal with its nested readers)

get_reader

GetReader

READERKEY

get_readers

GetReaders

— (optional STARTFROMKEY; no portal-id filter)

get_outputs

GetOutputs

— (optional STARTFROMKEY)

find_portals

GetPortals + GetReaders (composite)

query (search terms)

get_event_history

GetEventHistory

— (optional EVENTNAME/STARTDTTM/ENDDTTM/NEXTKEY)

list_events

ListEvents

— (optional RESOLVEPARTITIONNAMES, default true)

get_access_history

GetAccessHistory

— (optional STARTLOGID/AFTERLOGID/ORDER/MAXRECORDS/ENCODEDNUM/HOTSTAMP/CARDFORMAT/RESOLVENAMES/RESOLVEDESCRIPTIONS)

get_reader_access_history

GetAccessHistory + GetPerson + GetReaders (composite)

READERKEY (optional SCANWINDOW/MAXMATCHES/RESOLVEDESCRIPTIONS)

get_time_spec

GetTimeSpec

TIMESPECKEY

get_time_specs

GetTimeSpecs

— (optional STARTFROMKEY)

get_time_spec_group

GetTimeSpecGroup

TIMESPECGROUPKEY

get_time_spec_groups

GetTimeSpecGroups

— (optional STARTFROMKEY/RESOLVEMEMBERNAMES)

get_holiday

GetHoliday

HOLIDAYKEY

get_holidays

GetHolidays

— (no calling parameters)

get_portal_group

GetPortalGroup

PORTALGROUPKEY (optional RESOLVEGROUPNAMES)

get_portal_groups

GetPortalGroups

— (optional STARTFROMKEY/RESOLVEGROUPNAMES)

get_reader_group

GetReaderGroup

READERGROUPKEY

get_reader_groups

GetReaderGroups

— (optional STARTFROMKEY)

get_partitions

GetPartitions

—

get_udf_lists

GetUDFLists

—

get_udf_list_items

GetUDFListItems

UDFLISTKEY

get_elevators

GetElevators

— (optional STARTFROMKEY)

get_floors

GetFloors

— (optional STARTFROMKEY)

ping_app

PingApp

—

get_threat_levels

GetThreatLevels

— (optional ALLPARTITIONS)

get_unlock_window

GetPortalGroups + GetPortalGroup + GetTimeSpecGroups + GetTimeSpecs + GetHolidays + GetHoliday (composite)

—

get_daily_unlock_window

GetPortalGroups + GetPortalGroup + GetTimeSpecGroups + GetTimeSpecs + GetHolidays (composite)

—

get_portal_states

GetPortalStates

— (optional PORTALSTATES)

get_portal_statuses

GetPortalStatuses

— (optional ALLPARTITIONS/PORTALKEY/STATEKEY/PARTITIONKEY/LOCATIONKEY; the live state of each door, not its configuration)

get_locations

GetLocations

— (optional ALLPARTITIONS/STARTFROMKEY)

get_alarms

GetAlarms

— (optional ALLPARTITIONS/PARTITIONKEY/ID/EVENTID/ACTIVITYID/OWNERID)

get_picture

GetPicture

PERSONID (returns a Base64 JPEG in PICTURE; may be large)

get_virtual_credential_request

GetVirtualCredentialRequest

PERSONID, CARDFORMAT

get_mercury_panels

GetMercuryPanels

— (optional ALLPARTITIONS/PARTITIONKEY/MERCURYKEY/NAME)

get_mercury_panel

GetMercuryPanel

MERCURYKEY

get_network_nodes

GetNetworkNodes

— (optional ALLPARTITIONS/PARTITIONKEY/NODEKEY/UNIQUEIDENTIFIER/NAME)

get_network_node

GetNetworkNode

NODEKEY (optional PARTITIONKEY)

get_sios

GetSios

MERCURYKEY (no unfiltered SIO listing exists)

get_sio

GetSio

SIOKEY

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

lock_portal

LockPortal

PORTALKEY

write

unlock_portal

UnlockPortal

PORTALKEY

write

momentary_unlock_portal

MomentaryUnlockPortal

PORTALKEY

write

dog_on_next_exit_portal

DogOnNextExitPortal

PORTALKEY

write

activate_output

ActivateOutput

OUTPUTKEY

write

deactivate_output

DeactivateOutput

OUTPUTKEY

write

add_time_spec

AddTimeSpec

NAME

write

modify_time_spec

ModifyTimeSpec

TIMESPECKEY

write

add_time_spec_group

AddTimeSpecGroup

NAME

write

modify_time_spec_group

ModifyTimeSpecGroup

TIMESPECGROUPKEY

write

delete_time_spec

DeleteTimeSpec

TIMESPECKEY

destructive

delete_time_spec_group

DeleteTimeSpecGroup

TIMESPECGROUPKEY

destructive

add_holiday

AddHoliday

HOLIDAYNAME, STARTDATE, ENDDATE, HOLIDAYGROUPS

write

modify_holiday

ModifyHoliday

HOLIDAYKEY

write

delete_holiday

DeleteHoliday

HOLIDAYKEY

destructive

add_portal_group

AddPortalGroup

NAME, PORTALKEYS, UNLOCKTIMESPECGROUPKEY

write

modify_portal_group

ModifyPortalGroup

PORTALGROUPKEY, PORTALKEYS

write

delete_portal_group

DeletePortalGroup

PORTALGROUPKEY

destructive

add_reader_group

AddReaderGroup

NAME, READERKEYS

write

modify_reader_group

ModifyReaderGroup

READERGROUPKEY, READERKEYS

write

delete_reader_group

DeleteReaderGroup

READERGROUPKEY

destructive

add_access_level

AddAccessLevel

ACCESSLEVELNAME, TIMESPECGROUPKEY

write

modify_access_level

ModifyAccessLevel

ACCESSLEVELKEY, TIMESPECGROUPKEY

write

delete_access_level

DeleteAccessLevel

ACCESSLEVELKEY

destructive

add_access_level_group

AddAccessLevelGroup

NAME

write

modify_access_level_group

ModifyAccessLevelGroup

ACCESSLEVELGROUPKEY

write

delete_access_level_group

DeleteAccessLevelGroup

ACCESSLEVELGROUPKEY

destructive

add_person

AddPerson

LASTNAME

write

modify_person

ModifyPerson

PERSONID

write

remove_person

RemovePerson

PERSONID

destructive

add_credential

AddCredential

PERSONID, CARDFORMAT (+ ENCODEDNUM or HOTSTAMP)

write

modify_credential

ModifyCredential

PERSONID

write

remove_credential

RemoveCredential

PERSONID (+ CREDENTIALID or ENCODEDNUM/HOTSTAMP)

destructive

set_threat_level

SetThreatLevel

LEVELNAME (optional LOCATIONKEYS)

write

add_threat_level

AddThreatLevel

LEVELNAME

write

modify_threat_level

ModifyThreatLevel

LEVELNAME, SEQNUM, COLOR

write

remove_threat_level

RemoveThreatLevel

LEVELNAME

destructive

add_threat_level_group

AddThreatLevelGroup

LEVELGROUPNAME

write

modify_threat_level_group

ModifyThreatLevelGroup

LEVELGROUPNAME, LEVELNAMES

write

remove_threat_level_group

RemoveThreatLevelGroup

LEVELGROUPNAME

destructive

trigger_event

TriggerEvent

EVENTNAME, EVENTACTION

write

insert_activity

InsertActivity

ACTIVITYTYPE

write

add_partition

AddPartition

NAME, TIMEZONE

write

switch_partition

SwitchPartition

PARTITIONKEY

write

modify_udf_list_items

ModifyUDFListItems

UDFLISTKEY, LISTITEMS

write

set_portals_state

LockPortal / UnlockPortal / MomentaryUnlockPortal per portal, after GetPortals (composite)

action (portalKeys optional; omitted = every portal)

write

schedule_unlock_window

AddHoliday/ModifyHoliday, AddTimeSpec/ModifyTimeSpec, AddTimeSpecGroup/ModifyTimeSpecGroup, AddPortalGroup/ModifyPortalGroup, plus DeleteTimeSpec/DeleteHoliday of leftover managed segments, plus reads (composite)

start, end (portalKeys, acknowledgeSideEffects, dryRun optional)

write

cancel_unlock_window

ModifyPortalGroup, DeleteHoliday, ModifyTimeSpecGroup, DeleteTimeSpec — managed objects only — plus reads (composite)

—

write

schedule_daily_unlock_window

AddHoliday/ModifyHoliday, AddTimeSpec/ModifyTimeSpec, AddTimeSpecGroup/ModifyTimeSpecGroup, AddPortalGroup/ModifyPortalGroup, plus reads (composite)

startDate, endDate, dailyStartTime, dailyEndTime (portalKeys, acknowledgeSideEffects, dryRun optional)

write

cancel_daily_unlock_window

ModifyPortalGroup, DeleteHoliday, ModifyTimeSpecGroup, DeleteTimeSpec — managed objects only — plus reads (composite)

—

write

add_duty_log

AddDutyLog

PERSONID, LOGTEXT (optional ACTIVITYID/PARTITIONKEY)

write

add_virtual_credential_request

AddVirtualCredentialRequest

PERSONID, CARDFORMAT

write

remove_virtual_credential_request

RemoveVirtualCredentialRequest

PERSONID, CARDFORMAT

destructive

add_mercury_panel

AddMercuryPanel

NAME, TYPE, ENABLED, PARTITIONKEY, NETWORK (nested: IPADDRESS, TLSSECURE)

write

modify_mercury_panel

ModifyMercuryPanel

MERCURYKEY, NAME, ENABLED, NETWORK (nested: IPADDRESS, TLSSECURE)

write

delete_mercury_panel

DeleteMercuryPanel

MERCURYKEY

destructive

add_network_node

AddNetworkNode

NAME, TYPE, ENABLED, PARTITIONKEY, UNIQUEIDENTIFIER, DHCPENABLED

write

modify_network_node

ModifyNetworkNode

NODEKEY

write

delete_network_node

DeleteNetworkNode

NODEKEY

destructive

add_sio

AddSio

MERCURYKEY, NAME, MODEL, CHANNEL, ADDRESS, REVINPUT

write

modify_sio

ModifySio

SIOKEY, NAME, REVINPUT

write

delete_sio

DeleteSio

SIOKEY

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. portalKeys are keys only (use get_portals or find_portals to map names); an unknown key is refused before anything is written.

  • End of day on the NBAPI is 23:59 (the built-in Always uses it), and ENDTIME is 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 stated end minute (observed live: a window ending 08:27 relocked at 08:27:59 controller time). An end of 00:00 means "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 GetEventHistory carries 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_state is the immediate alternative: its UNLOCK is an Extended Unlock that lasts until LOCK, 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's ERRMSG text verbatim.

  • A <CODE>NOT FOUND</CODE> response (e.g. an unknown PERSONID) is returned as a normal, non-error result stating "not found" — it is not thrown as an exception.

  • SIGINT/SIGTERM trigger Logout for 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. portalKeys are keys only (use get_portals/find_portals to map names); an unknown key is refused before anything is written.

  • An overnight-crossing daily window is not supported: dailyEndTime must be strictly later than dailyStartTime (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-DD and times are HH: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 test

Runs 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:live

This 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 prints npm warn Unknown cli config "--go" and the flag never reaches the script — observed live, 2026-09-15). If --go doesn'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

unlock_portal

UnlockPortal (Extended Unlock)

lock_portal

lock_portal

LockPortal

—

momentary_unlock_portal

MomentaryUnlockPortal (relocks itself)

—

dog_on_next_exit_portal

DogOnNextExitPortal

lock_portal

activate_output

ActivateOutput on the portal's strike output

deactivate_output

deactivate_output

DeactivateOutput on the portal's strike output

—

set_portals_state_unlock

the real set_portals_state (setPortalsState) tool, action UNLOCK

set_portals_state_lock

set_portals_state_lock

set_portals_state, action LOCK

—

set_portals_state_momentary

set_portals_state, action MOMENTARY_UNLOCK (relocks itself)

—

set_threat_level

SetThreatLevel LEVELNAME=<--value>

set_threat_level --value Default

trigger_event_activate

TriggerEvent EVENTNAME=<--value> EVENTACTION=ACTIVATE PARTITIONID=1

trigger_event_deactivate

trigger_event_deactivate

TriggerEvent EVENTNAME=<--value> EVENTACTION=DEACTIVATE PARTITIONID=1

—

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 window

PowerShell: see the same-named caveat under "Live write smoke test" above — if --go is silently dropped, use npx tsx scripts/live-check-write-daily.ts --go instead (verified live, 2026-09-15, on portal 02OF01A: 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 real schedule_daily_unlock_window executor, prints an OBSERVE: ... line, polls get_daily_unlock_window every 30 s until one minute after relock, then calls cancel_daily_unlock_window and asserts the managed portal group is on Never with no managed holiday or time spec left (leftBehind is 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), and npm test never 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_picture wraps GetPicture and returns the Base64 JPEG unmodified.

  • Elevator and floor writes — not a choice: neither the v1 nor the v2 guide documents any Add/Modify/Delete command for elevators or floors, so get_elevators/get_floors are 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_alarms reads alarms, but this server does not drive an operator alarm queue.

  • StreamEvents / the persistent /appdevent/nbapi/event push feed

  • MAC-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_portals map 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 tools
check_connectionA
Read-only

Confirms the server can authenticate to the configured S2 NetBox controller and returns the NBAPI version string (wraps GetAPIVersion). No parameters required.

NameRequiredDescriptionDefault

No parameters

TDQS

A3.9/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_portalsA
Read-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.

NameRequiredDescriptionDefault
queryYesRequired. Search terms, e.g. "maintenance office", "electrical closet", or "01OF05".

TDQS

A4.3/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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_historyA
Read-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.

NameRequiredDescriptionDefault
ORDERNoOptional. Sort order for returned records.
HOTSTAMPNoOptional. Restrict results to this hot-stamp number.
AFTERLOGIDNoOptional. Return records strictly after this LOGID.
CARDFORMATNoOptional. Card format of ENCODEDNUM/HOTSTAMP.
ENCODEDNUMNoOptional. Restrict results to this encoded card number.
MAXRECORDSNoOptional. Maximum number of records to return.
STARTLOGIDNoOptional. Begin returning records at this LOGID.
RESOLVENAMESNoOptional (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.
RESOLVEDESCRIPTIONSNoOptional (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

A4/5.0
Behavior5/5

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.

Conciseness3/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_levelA
Read-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.

NameRequiredDescriptionDefault
ACCESSLEVELKEYYesRequired. The unique ACCESSLEVELKEY of the access level to retrieve.
RESOLVEGROUPNAMESNoOptional (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

A4/5.0
Behavior5/5

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.

Conciseness3/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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_groupB
Read-only

Returns the details of a single access level group for a given ACCESSLEVELGROUPKEY (wraps NBAPI GetAccessLevelGroup).

NameRequiredDescriptionDefault
ACCESSLEVELGROUPKEYYesRequired. The unique ACCESSLEVELGROUPKEY of the access level group to retrieve.

TDQS

B3.4/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_groupsB
Read-only

Lists access level groups configured on the NetBox system (wraps NBAPI GetAccessLevelGroups).

NameRequiredDescriptionDefault
PARTITIONKEYNoOptional. Per NBAPI GetAccessLevelGroups — only "0" is documented as allowed.
STARTFROMKEYNoOptional. Pagination cursor to continue listing from a previous call.

TDQS

B3.1/5.0
Behavior2/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_namesC
Read-only

Lists access level names configured on the NetBox system (wraps NBAPI GetAccessLevelNames).

NameRequiredDescriptionDefault
PARTITIONKEYNoOptional. Per NBAPI GetAccessLevelNames — only "0" is documented as allowed.
STARTFROMNAMENoOptional. Pagination cursor (name) to continue listing from a previous call.

TDQS

C2.9/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_levelsC
Read-only

Lists access levels configured on the NetBox system (wraps NBAPI GetAccessLevels).

NameRequiredDescriptionDefault
WANTKEYNoOptional. Per NBAPI GetAccessLevels.
PARTITIONKEYNoOptional. Per NBAPI GetAccessLevels — only "0" is documented as allowed.
STARTFROMKEYNoOptional. Pagination cursor (key) to continue listing from a previous call.
STARTFROMNAMENoOptional. Pagination cursor (name) to continue listing from a previous call.

TDQS

C2.9/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_alarmsA
Read-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.

NameRequiredDescriptionDefault
IDNoOptional. Return the alarm with this alarm ID.
EVENTIDNoOptional. Return alarms raised by this event ID.
OWNERIDNoOptional. Return alarms owned by this operator/person ID.
ACTIVITYIDNoOptional. Return alarms for this activity ID.
PARTITIONKEYNoOptional. Return alarms for this partition key only.
ALLPARTITIONSNoOptional. "TRUE" to return alarms across all partitions.

TDQS

A3.9/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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_detailsA
Read-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.

NameRequiredDescriptionDefault
CARDFORMATYesRequired. The card format of ENCODEDNUM.
ENCODEDNUMYesRequired. The encoded card number whose access details should be retrieved.
MAXRECORDSNoOptional. Maximum number of access records to return.
OLDESTDTTMNoOptional. Oldest date/time to include in the returned access records.
RESOLVENAMESNoOptional (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).
RESOLVEDESCRIPTIONSNoOptional (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

A4.5/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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_formatsB
Read-only

Returns the card formats configured on the NetBox system (wraps NBAPI GetCardFormats). No parameters required.

NameRequiredDescriptionDefault

No parameters

TDQS

B3.1/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_windowC
Read-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.

NameRequiredDescriptionDefault

No parameters

TDQS

C2.9/5.0
Behavior3/5

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.

Conciseness2/5

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.

Completeness3/5

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.

Parameters4/5

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.

Purpose3/5

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.

Usage Guidelines2/5

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_elevatorsB
Read-only

Lists elevators configured on the NetBox system (wraps NBAPI GetElevators).

NameRequiredDescriptionDefault
STARTFROMKEYNoOptional. Pagination cursor to continue listing from a previous call.

TDQS

B3.2/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_historyB
Read-only

Returns historical NetBox events for an optional event name/date range (wraps NBAPI GetEventHistory).

NameRequiredDescriptionDefault
ENDDTTMNoOptional. End of the date/time range to query (NBAPI-documented format).
NEXTKEYNoOptional. Pagination continuation cursor from a previous call.
EVENTNAMENoOptional. Restrict results to this event name.
STARTDTTMNoOptional. Start of the date/time range to query (NBAPI-documented format).

TDQS

B3.2/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_floorsB
Read-only

Lists floors configured on the NetBox system (wraps NBAPI GetFloors).

NameRequiredDescriptionDefault
STARTFROMKEYNoOptional. Pagination cursor to continue listing from a previous call.

TDQS

B3.2/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_guideA
Read-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.

NameRequiredDescriptionDefault
topicNoOptional. 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

A4.3/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_holidayA
Read-only

Returns the details of a single holiday for a given HOLIDAYKEY (wraps NBAPI GetHoliday).

NameRequiredDescriptionDefault
HOLIDAYKEYYesRequired. The unique HOLIDAYKEY of the holiday to retrieve.

TDQS

A3.5/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_holidaysA
Read-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).

NameRequiredDescriptionDefault

No parameters

TDQS

A4.5/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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_locationsA
Read-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.

NameRequiredDescriptionDefault
STARTFROMKEYNoOptional. 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".)
ALLPARTITIONSNoOptional. "TRUE" to list locations across all partitions.

TDQS

A3.7/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_panelA
Read-only

Returns the details of a single Mercury panel for a given MERCURYKEY (wraps NBAPI GetMercuryPanel).

NameRequiredDescriptionDefault
MERCURYKEYYesRequired. The key of the Mercury panel to retrieve. Use get_mercury_panels to discover keys.

TDQS

A3.6/5.0
Behavior2/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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_panelsA
Read-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.

NameRequiredDescriptionDefault
NAMENoOptional. Restrict the listing to Mercury panels with this name.
MERCURYKEYNoOptional. Restrict the listing to this Mercury panel key.
PARTITIONKEYNoOptional. Restrict the listing to this partition key.
ALLPARTITIONSNoOptional. "TRUE" to list Mercury panels across all partitions.

TDQS

A3.6/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_nodeB
Read-only

Returns the details of a single network node for a given NODEKEY (wraps NBAPI GetNetworkNode).

NameRequiredDescriptionDefault
NODEKEYYesRequired. The key of the network node to retrieve. Use get_network_nodes to discover keys.
PARTITIONKEYNoOptional. Key of the partition the node belongs to.

TDQS

B3.4/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_nodesA
Read-only

Lists the network nodes configured on the NetBox system, optionally filtered (wraps NBAPI GetNetworkNodes).

NameRequiredDescriptionDefault
NAMENoOptional. Restrict the listing to network nodes with this name.
NODEKEYNoOptional. Restrict the listing to this network node key.
PARTITIONKEYNoOptional. Restrict the listing to this partition key.
ALLPARTITIONSNoOptional. "TRUE" to list network nodes across all partitions.
UNIQUEIDENTIFIERNoOptional. Restrict the listing to the node with this 16-hex-digit unique identifier.

TDQS

A3.6/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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_outputsC
Read-only

Lists auxiliary outputs configured on the NetBox system (wraps NBAPI GetOutputs).

NameRequiredDescriptionDefault
STARTFROMKEYNoOptional. Pagination cursor to continue listing from a previous call.

TDQS

C2.9/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_partitionsB
Read-only

Lists partitions configured on the NetBox system (wraps NBAPI GetPartitions). No parameters required.

NameRequiredDescriptionDefault

No parameters

TDQS

B3.1/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_personB
Read-only

Returns the full person record for a given PERSONID (wraps NBAPI GetPerson).

NameRequiredDescriptionDefault
PERSONIDYesRequired. The unique PERSONID of the person record to retrieve.
ALLPARTITIONSNoOptional. Per NBAPI GetPerson.
WANTCREDENTIALIDNoOptional. Include CREDENTIALID values on returned access cards.
ACCESSLEVELDETAILSNoOptional. Include full access level details in the response.

TDQS

B3.3/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_pictureA
Read-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".

NameRequiredDescriptionDefault
PERSONIDYesRequired. The PERSONID of the person whose picture should be retrieved.

TDQS

A4.4/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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_groupA
Read-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.

NameRequiredDescriptionDefault
PORTALGROUPKEYYesRequired. The unique PORTALGROUPKEY of the portal group to retrieve.
RESOLVEGROUPNAMESNoOptional (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

A4.3/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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_groupsA
Read-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.

NameRequiredDescriptionDefault
STARTFROMKEYNoOptional. Pagination cursor to continue listing from a previous call.
RESOLVEGROUPNAMESNoOptional (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

A3.8/5.0
Behavior5/5

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.

Conciseness3/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_portalsA
Read-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.

NameRequiredDescriptionDefault
STARTFROMKEYNoOptional. Pagination cursor — the NEXTKEY from a previous call, to continue listing.
RESOLVEDESCRIPTIONSNoOptional (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

A4.6/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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_statesA
Read-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.

NameRequiredDescriptionDefault
PORTALSTATESNoOptional. "TRUE" returns portal states across all partitions; omitted, the controller returns states for the default or currently switched partition.

TDQS

A4.3/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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_statusesA
Read-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.

NameRequiredDescriptionDefault
STATEKEYNoOptional. Report only portals currently in this state (see get_portal_states).
PORTALKEYNoOptional. Report status for this portal only.
LOCATIONKEYNoOptional. Report status for portals at this location only (see get_locations).
PARTITIONKEYNoOptional. Report status for portals in this partition only.
ALLPARTITIONSNoOptional. "TRUE" to report portal status across all partitions.

TDQS

A4.1/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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_readerB
Read-only

Returns the details of a single reader for a given READERKEY (wraps NBAPI GetReader).

NameRequiredDescriptionDefault
READERKEYYesRequired. The unique READERKEY of the reader to retrieve.

TDQS

B3.2/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_historyA
Read-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.

NameRequiredDescriptionDefault
READERKEYYesRequired. Only access records for this reader are returned.
MAXMATCHESNoOptional. Maximum number of matches to include, in chronological order. Defaults to 100.
SCANWINDOWNoOptional. Number of most-recent system-wide access records to scan. Defaults to 2000.
RESOLVEDESCRIPTIONSNoOptional (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

A4.4/5.0
Behavior5/5

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.

Conciseness3/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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_groupC
Read-only

Returns the details of a single reader group for a given READERGROUPKEY (wraps NBAPI GetReaderGroup).

NameRequiredDescriptionDefault
READERGROUPKEYYesRequired. The unique READERGROUPKEY of the reader group to retrieve.

TDQS

C2.9/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness2/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_groupsC
Read-only

Lists reader groups configured on the NetBox system (wraps NBAPI GetReaderGroups).

NameRequiredDescriptionDefault
STARTFROMKEYNoOptional. Pagination cursor to continue listing from a previous call.

TDQS

C2.9/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_readersA
Read-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.

NameRequiredDescriptionDefault
STARTFROMKEYNoOptional. Pagination cursor to continue listing from a previous call.

TDQS

A4.2/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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_sioA
Read-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.

NameRequiredDescriptionDefault
SIOKEYYesRequired. The key of the SIO to retrieve. Use get_sios to discover keys.

TDQS

A4.1/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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_siosA
Read-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.

NameRequiredDescriptionDefault
MERCURYKEYYesRequired. The Mercury panel key whose SIOs should be listed. Use get_mercury_panels to discover keys.

TDQS

A4/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines4/5

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_levelsB
Read-only

Lists threat levels configured on the NetBox system, optionally filtered (wraps NBAPI GetThreatLevels).

NameRequiredDescriptionDefault
ALLPARTITIONSNoOptional. Per NBAPI GetThreatLevels filter.

TDQS

B3.2/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_specB
Read-only

Returns the details of a single time spec for a given TIMESPECKEY (wraps NBAPI GetTimeSpec).

NameRequiredDescriptionDefault
TIMESPECKEYYesRequired. The unique TIMESPECKEY of the time spec to retrieve.

TDQS

B3.4/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_groupA
Read-only

Returns the details of a single time spec group for a given TIMESPECGROUPKEY (wraps NBAPI GetTimeSpecGroup).

NameRequiredDescriptionDefault
TIMESPECGROUPKEYYesRequired. The unique TIMESPECGROUPKEY of the time spec group to retrieve.

TDQS

A3.6/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines2/5

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_groupsA
Read-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).

NameRequiredDescriptionDefault
STARTFROMKEYNoOptional. Pagination cursor to continue listing from a previous call.
RESOLVEMEMBERNAMESNoOptional (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

A4.4/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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_specsB
Read-only

Lists time specs configured on the NetBox system (wraps NBAPI GetTimeSpecs).

NameRequiredDescriptionDefault
STARTFROMKEYNoOptional. Pagination cursor to continue listing from a previous call.

TDQS

B3.3/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_itemsB
Read-only

Returns the items of a single UDF list for a given UDFLISTKEY (wraps NBAPI GetUDFListItems).

NameRequiredDescriptionDefault
UDFLISTKEYYesRequired. The unique UDFLISTKEY of the UDF list to retrieve.

TDQS

B3.2/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_listsB
Read-only

Lists user-defined field (UDF) lists configured on the NetBox system (wraps NBAPI GetUDFLists). No parameters required.

NameRequiredDescriptionDefault

No parameters

TDQS

B3.2/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_windowA
Read-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.

NameRequiredDescriptionDefault

No parameters

TDQS

A3.5/5.0
Behavior4/5

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.

Conciseness3/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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_requestA
Read-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.

NameRequiredDescriptionDefault
PERSONIDYesRequired. The PERSONID the credential is assigned to.
CARDFORMATYesRequired. Name of the card format used to decode the credential (see get_card_formats).

TDQS

A4.3/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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_eventsA
Read-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.

NameRequiredDescriptionDefault
RESOLVEPARTITIONNAMESNoOptional (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

A3.6/5.0
Behavior4/5

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.

Conciseness3/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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_appA
Read-only

Pings the NetBox NBAPI application to confirm it is responsive (wraps NBAPI PingApp). No parameters required.

NameRequiredDescriptionDefault

No parameters

TDQS

A3.8/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines2/5

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_dataB
Read-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.

NameRequiredDescriptionDefault
UDF1NoOptional. Search filter on user-defined field UDF1.
UDF2NoOptional. Search filter on user-defined field UDF2.
UDF3NoOptional. Search filter on user-defined field UDF3.
UDF4NoOptional. Search filter on user-defined field UDF4.
UDF5NoOptional. Search filter on user-defined field UDF5.
UDF6NoOptional. Search filter on user-defined field UDF6.
UDF7NoOptional. Search filter on user-defined field UDF7.
UDF8NoOptional. Search filter on user-defined field UDF8.
UDF9NoOptional. Search filter on user-defined field UDF9.
NOTESNoOptional. Match on the person record's notes text.
UDF10NoOptional. Search filter on user-defined field UDF10.
UDF11NoOptional. Search filter on user-defined field UDF11.
UDF12NoOptional. Search filter on user-defined field UDF12.
UDF13NoOptional. Search filter on user-defined field UDF13.
UDF14NoOptional. Search filter on user-defined field UDF14.
UDF15NoOptional. Search filter on user-defined field UDF15.
UDF16NoOptional. Search filter on user-defined field UDF16.
UDF17NoOptional. Search filter on user-defined field UDF17.
UDF18NoOptional. Search filter on user-defined field UDF18.
UDF19NoOptional. Search filter on user-defined field UDF19.
UDF20NoOptional. Search filter on user-defined field UDF20.
DELETEDNoOptional. Include/exclude deleted person records.
HOTSTAMPNoOptional. Match on a card hot-stamp number.
LASTNAMENoOptional. Match on the person's last name.
PERSONIDNoOptional. Match on a specific PERSONID.
FIRSTNAMENoOptional. Match on the person's first name.
CARDFORMATNoOptional. Match persons holding a card in this card format.
CARDSTATUSNoOptional. Match persons holding a card with this card status name.
MIDDLENAMENoOptional. Match on the person's middle name.
MSUENABLEDNoOptional. Match on whether MSU mobile credentials are enabled ("TRUE"/"FALSE").
ACCESSLEVELNoOptional. Match persons assigned this access level.
MOBILEPHONENoOptional. Match on the person's mobile phone number.
CONTACTEMAILNoOptional. Match on the person's office email address.
ALLPARTITIONSNoOptional. Search across all partitions.
NEWESTLASTMODNoOptional. Only include records last modified on/before this date/time.
OLDESTLASTMODNoOptional. Only include records last modified on/after this date/time.
RAWCARDNUMBERNoOptional. Match on a raw (unformatted) card number.
VEHICLELICNUMNoOptional. Match on a vehicle license plate number.
VEHICLETAGNUMNoOptional. Match on a vehicle tag number.
WILDCARDSEARCHNoOptional. Treat text filters as wildcard patterns.
CASEINSENSITIVENoOptional. Perform a case-insensitive match.
WANTCREDENTIALIDNoOptional. Include CREDENTIALID values on returned access cards.
ACCESSLEVELDETAILSNoOptional. Include full access level details in the response.
BLUEDIAMONDENABLEDNoOptional. Match on whether BlueDiamond mobile credentials are enabled ("TRUE"/"FALSE").

TDQS

B3.4/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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.

  1. 13 tool updatesv0.4.0
    • Addedget_alarms
    • Addedget_guide
    • Addedget_locations
    • Addedget_mercury_panel
    • Addedget_mercury_panels
    • Addedget_network_node
    • Addedget_network_nodes
    • Addedget_picture
    • Addedget_portal_states
    • Addedget_portal_statuses
    • Addedget_sio
    • Addedget_sios
    • Addedget_virtual_credential_request
  2. 14 tool updatesv0.3.0
    • Changedget_access_history4 fields changed
      • removedInput schema / properties / NEWESTDTTM
        Removed value: -{
        -  "description": "Optional. Newest date/time to include.",
        -  "type": "string"
        -}
      • removedInput schema / properties / OLDESTDTTM
        Removed value: -{
        -  "description": "Optional. Oldest date/time to include.",
        -  "type": "string"
        -}
      • addedInput schema / properties / RESOLVEDESCRIPTIONS
        Added 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"
        +}
      • addedInput schema / properties / RESOLVENAMES
        Added 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"
        +}
    • Changedget_access_level1 field changed
      • addedInput schema / properties / RESOLVEGROUPNAMES
        Added 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"
        +}
    • Changedget_access_level_groups1 field changed
      • addedInput schema / properties / PARTITIONKEY
        Added value: +{
        +  "description": "Optional. Per NBAPI GetAccessLevelGroups — only \"0\" is documented as allowed.",
        +  "type": "string"
        +}
    • Changedget_access_levels1 field changed
      • addedInput schema / properties / PARTITIONKEY
        Added value: +{
        +  "description": "Optional. Per NBAPI GetAccessLevels — only \"0\" is documented as allowed.",
        +  "type": "string"
        +}
    • Changedget_card_access_details2 fields changed
      • addedInput schema / properties / RESOLVEDESCRIPTIONS
        Added 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"
        +}
      • addedInput schema / properties / RESOLVENAMES
        Added 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"
        +}
    • Changedget_holidays2 fields changed
      • removedInput schema / additionalProperties
        Removed value: -false
      • removedInput schema / properties / STARTFROMKEY
        Removed value: -{
        -  "description": "Optional. Pagination cursor to continue listing from a previous call.",
        -  "type": "string"
        -}
    • Changedget_portal_group1 field changed
      • addedInput schema / properties / RESOLVEGROUPNAMES
        Added 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"
        +}
    • Changedget_portal_groups1 field changed
      • addedInput schema / properties / RESOLVEGROUPNAMES
        Added 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"
        +}
    • Changedget_portals1 field changed
      • addedInput schema / properties / RESOLVEDESCRIPTIONS
        Added 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"
        +}
    • Addedget_reader_access_history
    • Addedget_threat_levels
    • Changedget_time_spec_groups1 field changed
      • addedInput schema / properties / RESOLVEMEMBERNAMES
        Added 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"
        +}
    • Changedlist_events2 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • addedInput schema / properties / RESOLVEPARTITIONNAMES
        Added 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"
        +}
    • Changedsearch_person_data9 fields changed
      • addedInput schema / properties / BLUEDIAMONDENABLED
        Added value: +{
        +  "description": "Optional. Match on whether BlueDiamond mobile credentials are enabled (\"TRUE\"/\"FALSE\").",
        +  "type": "string"
        +}
      • addedInput schema / properties / CARDFORMAT
        Added value: +{
        +  "description": "Optional. Match persons holding a card in this card format.",
        +  "type": "string"
        +}
      • addedInput schema / properties / CARDSTATUS
        Added value: +{
        +  "description": "Optional. Match persons holding a card with this card status name.",
        +  "type": "string"
        +}
      • addedInput schema / properties / CONTACTEMAIL
        Added value: +{
        +  "description": "Optional. Match on the person's office email address.",
        +  "type": "string"
        +}
      • addedInput schema / properties / MOBILEPHONE
        Added value: +{
        +  "description": "Optional. Match on the person's mobile phone number.",
        +  "type": "string"
        +}
      • addedInput schema / properties / MSUENABLED
        Added value: +{
        +  "description": "Optional. Match on whether MSU mobile credentials are enabled (\"TRUE\"/\"FALSE\").",
        +  "type": "string"
        +}
      • addedInput schema / properties / NOTES
        Added value: +{
        +  "description": "Optional. Match on the person record's notes text.",
        +  "type": "string"
        +}
      • addedInput schema / properties / VEHICLELICNUM
        Added value: +{
        +  "description": "Optional. Match on a vehicle license plate number.",
        +  "type": "string"
        +}
      • addedInput schema / properties / VEHICLETAGNUM
        Added value: +{
        +  "description": "Optional. Match on a vehicle tag number.",
        +  "type": "string"
        +}
  3. 36 tool updatesv0.2.3
    • First observedcheck_connection
    • First observedfind_portals
    • First observedget_access_history
    • First observedget_access_level
    • First observedget_access_level_group
    • First observedget_access_level_groups
    • First observedget_access_level_names
    • First observedget_access_levels
    • First observedget_card_access_details
    • First observedget_card_formats
    • First observedget_daily_unlock_window
    • First observedget_elevators
    • First observedget_event_history
    • First observedget_floors
    • First observedget_holiday
    • First observedget_holidays
    • First observedget_outputs
    • First observedget_partitions
    • First observedget_person
    • First observedget_portal_group
    • First observedget_portal_groups
    • First observedget_portals
    • First observedget_reader
    • First observedget_reader_group
    • First observedget_reader_groups
    • First observedget_readers
    • First observedget_time_spec
    • First observedget_time_spec_group
    • First observedget_time_spec_groups
    • First observedget_time_specs
    • First observedget_udf_list_items
    • First observedget_udf_lists
    • First observedget_unlock_window
    • First observedlist_events
    • First observedping_app
    • First observedsearch_person_data

TDQS

B3.1/5.0

Scored across 51 tools

Disambiguation4/5

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 Consistency4/5

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.

Tool Count1/5

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.

Completeness2/5

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

ActivityMaintained
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Provides 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.
    33
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables 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