Lint tool definitions for AI agents
Project description
toollint
Lint tool definitions for AI agents. Checks descriptions, parameter docs, and semantic overlap so models pick the right tool.
Install
pip install toollint
# With semantic overlap detection:
pip install toollint[semantic]
Requires Python >= 3.10.
Quick start
toollint --src ./src/ # scan + lint in one command
toollint build --src ./src/ # just collect, write _build/tools.json
toollint check # lint _build/tools.json
toollint check --format json # CI-friendly output
toollint check --quiet # no output on clean runs
cat tools.json | toollint check --stdin # read from pipe
Defining tools
Three ways to define tools. Mix and match.
Option A: @tool decorator
from toollint import tool
@tool
def search_web(query: str, max_results: int = 10) -> str:
"""Search the web for current information.
Args:
query: The search query string.
max_results: Maximum number of results.
"""
...
# Override name and description via decorator kwargs
@tool(name="web_search", description="Search the internet")
def search(query: str) -> str:
...
# Async functions work too
@tool
async def fetch_data(url: str) -> dict:
"""Fetch data from a URL.
Args:
url: The URL to fetch.
"""
...
The @tool decorator is an identity function at runtime — zero overhead. The AST scanner finds it statically. Only matches @tool imported from toollint; from otherlib import tool is ignored.
Docstrings are parsed in Google, Sphinx, and NumPy styles. First match wins.
Option B: Tool base class
from toollint import Tool
class SearchDocs(Tool):
name = "search_docs"
description = "Search documentation for a query string"
def run(self, query: str, max_results: int = 10) -> str:
"""Run the search.
Args:
query: The search query.
max_results: Maximum results.
"""
...
# with type annotations preserved
from typing import Optional, Literal
class CreateFile(Tool):
name = "create_file"
description = "Create a new file with the given content"
def run(self, path: str, content: str, mode: Literal["write", "append"] = "write") -> str:
"""Create a file.
Args:
path: File path.
content: File content.
mode: Write mode.
"""
...
The AST scanner finds classes inheriting from toollint.Tool. Extracts name and description class attributes, then run() method signature for parameters. self is automatically excluded from the parameter list.
Only matches Tool imported from toollint. from otherlib import Tool is ignored.
Option C: tools/*.json
{
"name": "extra_tool",
"description": "An extra tool from JSON config",
"parameters": [
{
"name": "query",
"type": "str",
"description": "The search query",
"required": true
}
]
}
Hand-written JSON definitions. Files can be a JSON array or NDJSON (one object per line). All *.json files in tools/ are merged at build time. Duplicate names: last file wins with a warning.
Public API
Everything importable from toollint root. Internal modules start with _ — not part of the public contract.
Classes
ToolDef
A complete tool definition. The central data structure.
from toollint import ToolDef, ParameterDef
tool = ToolDef(
name="search_web",
description="Search the web for current information",
source_file="src/search.py",
source_line=42,
parameters=[
ParameterDef(
name="query",
type="str",
description="The search query string",
required=True,
),
ParameterDef(
name="max_results",
type="int",
required=False,
),
],
)
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Tool name |
description |
str | None |
None |
Tool description. None triggers "missing description" error |
source_file |
str | None |
None |
Source file path for clickable output |
source_line |
int | None |
None |
Source line number |
parameters |
list[ParameterDef] |
[] |
Parameter list, nested arbitrarily |
ParameterDef
A parameter within a tool definition.
from toollint import ParameterDef
param = ParameterDef(
name="query",
type="str",
description="The search query",
required=True,
parameters=[], # nested params, e.g. for object/dict types
)
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Parameter name |
type |
str |
"Any" |
Type string, e.g. "str", "int", "Optional[str]" |
description |
str | None |
None |
Parameter description. None triggers warning |
required |
bool |
True |
Whether the parameter is required |
parameters |
list[ParameterDef] |
[] |
Nested parameters (dict/object children) |
Report
Result of running checks. Returned by scan().
from toollint import scan, ToolDef
tools = [ToolDef(name="a", description="..."), ToolDef(name="b", description="...")]
report = scan(tools)
print(report.ok) # bool: True if no errors
print(report.errors) # list[Finding]: error-level findings
print(report.warnings) # list[Finding]: warning-level findings
print(report.skipped) # list[str]: skipped check names
Finding
A single lint issue. Returned in report.errors and report.warnings.
finding = report.errors[0]
finding.check # str: check name, e.g. "duplicate_names"
finding.severity # Severity enum: ERROR or WARNING
finding.message # str: human-readable description
finding.tools # list[str]: tool names involved
finding.files # list[str]: source files involved
Decorators and base classes
tool
Runtime no-op decorator. AST scanner finds it statically.
from toollint import tool
@tool
def my_tool(x: str) -> str:
"""Description here.
Args:
x: The parameter.
"""
...
@tool(name="custom_name", description="Override everything")
def other(y: int) -> str:
...
Tool
Abstract base class. Subclass and set name + description class attributes.
from toollint import Tool
class MyTool(Tool):
name = "my_tool"
description = "What this tool does"
def run(self, input: str) -> str:
...
Functions
scan(tools, threshold=0.85) -> Report
Run all checks on a list of ToolDef objects. Returns a Report.
from toollint import scan, ToolDef
tools = [
ToolDef(name="a", description="Search the web for info"),
ToolDef(name="b", description="Look up things on the internet"),
]
report = scan(tools)
if not report.ok:
for finding in report.errors:
print(f"ERROR: {finding.message}")
| Parameter | Type | Default | Description |
|---|---|---|---|
tools |
list[ToolDef] |
required | Tool definitions to check |
threshold |
float |
0.85 |
Cosine similarity threshold for overlap detection |
tools_from_json(path) -> list[ToolDef]
Load tool definitions from a JSON file. Handles both JSON arrays and NDJSON.
from toollint import tools_from_json
from pathlib import Path
tools = tools_from_json(Path("_build/tools.json"))
Severity
from toollint._engine._checks import Severity
Severity.ERROR # "error"
Severity.WARNING # "warning"
Import from toollint._engine._checks — intentionally internal. Consumers read finding.severity on the Finding object, don't construct Severity values directly.
Checks
| Check | Severity | Trigger |
|---|---|---|
semantic_overlap |
Error | Two descriptions have cosine similarity > threshold (default 0.85) |
duplicate_names |
Error | Two tools share the exact same name |
missing_description |
Error | Tool has description=None or "" |
required_after_optional |
Error | A required parameter follows an optional one (OpenAI/Anthropic reject this schema) |
short_description |
Warning | Description has < 10 words OR doesn't start with an action verb |
missing_param_description |
Warning | Any parameter at any nesting level has no description |
Semantic overlap requires pip install toollint[semantic]. All other checks run with zero ML dependencies.
CLI reference
toollint --src ./src/ build + check (default)
toollint build --src ./src/ collect tools, write _build/tools.json
toollint check lint _build/tools.json
toollint check tools.json lint a specific file
toollint check --stdin read JSON array or NDJSON from pipe
| Flag | Applicable to | Default | Description |
|---|---|---|---|
--src PATH |
default, build |
./src/ |
Python source directory to scan |
--format rich|json |
default, check |
rich |
Output format |
--quiet |
default, check |
off | Suppress output when no errors |
--threshold FLOAT |
default, check |
0.85 |
Similarity threshold |
Exit codes: 0 = clean or warnings only, 1 = errors found, 2 = configuration error.
CI integration
# .github/workflows/toollint.yml
name: toollint
on: [push, pull_request]
jobs:
lint-tools:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install toollint[semantic]
- run: toollint --src ./src/
Pre-commit hook:
# .pre-commit-config.yaml
- repo: local
hooks:
- id: toollint
name: toollint
entry: toollint
language: system
pass_filenames: false
License
Apache-2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file toollint-0.1.1.tar.gz.
File metadata
- Download URL: toollint-0.1.1.tar.gz
- Upload date:
- Size: 46.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65ad3ad3a42d646327d8bd4ae479bca9fdaee20218c36896aa12b59f63746d59
|
|
| MD5 |
23ca188e4370a50afe9f1a9c56523fea
|
|
| BLAKE2b-256 |
29a40a0cbc984a9bbb1f4ab947195e9c011702e5f16906853f09865fe845403c
|
Provenance
The following attestation bundles were made for toollint-0.1.1.tar.gz:
Publisher:
ci.yml on Sheerabth/toollint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
toollint-0.1.1.tar.gz -
Subject digest:
65ad3ad3a42d646327d8bd4ae479bca9fdaee20218c36896aa12b59f63746d59 - Sigstore transparency entry: 2202094965
- Sigstore integration time:
-
Permalink:
Sheerabth/toollint@785a81c961ae499ec5d5aa6c6ccd95c96accf91f -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Sheerabth
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@785a81c961ae499ec5d5aa6c6ccd95c96accf91f -
Trigger Event:
push
-
Statement type:
File details
Details for the file toollint-0.1.1-py3-none-any.whl.
File metadata
- Download URL: toollint-0.1.1-py3-none-any.whl
- Upload date:
- Size: 22.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e8bad7d7910af87de01a8c4cbbbe1893a670db32f22dc1699c0198d1621d1da8
|
|
| MD5 |
9029dc69e25f2714afe6f8809c1bf8d0
|
|
| BLAKE2b-256 |
2de8b84fed763182789d27ec3a5833303213d2b787025862fdbf3de3cd299aaf
|
Provenance
The following attestation bundles were made for toollint-0.1.1-py3-none-any.whl:
Publisher:
ci.yml on Sheerabth/toollint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
toollint-0.1.1-py3-none-any.whl -
Subject digest:
e8bad7d7910af87de01a8c4cbbbe1893a670db32f22dc1699c0198d1621d1da8 - Sigstore transparency entry: 2202095011
- Sigstore integration time:
-
Permalink:
Sheerabth/toollint@785a81c961ae499ec5d5aa6c6ccd95c96accf91f -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Sheerabth
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@785a81c961ae499ec5d5aa6c6ccd95c96accf91f -
Trigger Event:
push
-
Statement type: