Adding a tool¶
neon_list_releases is the smallest real example; this is its shape.
1. Models¶
class ListReleasesIn(NeonInput): # snake_case fields; camelCase accepted; extras rejected
include_artifact_urls: bool = Field(False, description="Include signed manifest URLs.")
class ReleaseList(ToolResultBase): # envelope: resolved, notes, nextSteps, source
items: list[ReleaseSummary]
latest_release: str | None = None # dumped as latestRelease
page: Page
Every input field needs a description (it is what the model reads). Output keys use
NEON's own camelCase names where NEON has the field. Long lists declare
budget_list so results can be trimmed to the size budget.
2. Register the handler¶
@register_tool(
"neon_list_releases",
title="List NEON data releases",
description="... Next: call neon_get_release for one release's products and DOIs.",
input_model=ListReleasesIn,
output_model=ReleaseList,
surface="releases",
endpoints=["GET /releases"],
)
async def neon_list_releases(args: ListReleasesIn, ctx: ToolContext) -> ReleaseList:
releases = await ctx.catalog.releases(token=ctx.token, stats=ctx.stats)
...
Rules the registry enforces at import: names match ^neon_[a-z_]+$, descriptions are
at most 600 characters and end with a Next: sentence. Declare requires_token=True
for token-only endpoints (the registry then fails fast without one), keep annotations
honest, and list every NEON endpoint the handler calls. Import the module from
tools/__init__.py.
3. Fixtures and tests¶
Add recorded, shrunk, scrubbed responses to tests/fixtures/ and routes to
tests/fixture_router.py (unrouted requests fail the test). Test through
tests.helpers.call_ok, which also validates the result against the tool's
outputSchema: happy path, each filter, paging, error mapping, token fail-fast, cache
reuse.
4. Regenerate¶
python scripts/gen_tools_reference.py # docs/tools/reference.md
python scripts/gen_llms_txt.py # docs/llms.txt, docs/llms-full.txt
Mention the tool on its family page and add a docs/log.md entry.