# How to Manually Define Tool Schemas as JSON in Needle 2: A Complete Guide

> Learn to manually define tool schemas as JSON in Needle 2. Bypass the decorator and supply a JSON schema directly to needle.Needle for greater control over your tools.

- Repository: [Cactus Compute, Inc./needle](https://github.com/cactus-compute/needle)
- Tags: how-to-guide
- Published: 2026-08-14

---

**You can bypass the `@needle.tool` decorator and supply a JSON schema directly to `needle.Needle(tools=...)` using the same structure that `build_schema` generates internally, containing `name`, `description`, and a JSON Schema `parameters` object.**

Needle 2 is an open-source agent framework that discovers tools through JSON-encoded schemas. While the `@needle.tool` decorator automatically generates these schemas from Python functions, many production scenarios require you to manually define tool schemas as JSON in Needle 2—whether the tool lives outside your codebase, is dynamically generated, or requires explicit control over the interface exposed to the LLM.

## Understanding the JSON Schema Structure

According to the source code in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), the `build_schema` function produces a specific JSON structure that you must replicate when defining schemas manually. The schema must be a dictionary containing three top-level keys:

- `name`: The function identifier the model will invoke
- `description`: Human-readable documentation shown to the LLM (optional but recommended)
- `parameters`: A JSON Schema object with `type: "object"`, a `properties` map, and optionally a `required` list

Inside each property definition, you can specify standard JSON Schema types (`string`, `integer`, `boolean`, etc.) and validation constraints such as `description`, `enum`, `minimum`, `maximum`, `pattern`, and `minLength`.

## Creating Hand-Written Tool Schemas

### Basic JSON Structure

The documentation in [`doc/apis.md`](https://github.com/cactus-compute/needle/blob/main/doc/apis.md) demonstrates the canonical format for manual schema definition. Here is a complete example showing a `set_lights` tool:

```python
import needle

tools = [{
    "name": "set_lights",
    "description": "Turn a room's lights on or off and set brightness",
    "parameters": {
        "type": "object",
        "properties": {
            "room": {"type": "string", "description": "which room to control"},
            "on": {"type": "boolean"},
            "brightness": {"type": "integer", "minimum": 0, "maximum": 100},
        },
        "required": ["room", "on"],
    },
}]

agent = needle.Needle(tools=tools)
agent.run("turn the kitchen lights on at 70%")

```

The agent treats this hand-written schema exactly like one generated by the decorator, using the same grammar-constrained decoding and validation pipeline internally.

### Loading Schemas from External JSON Files

For large tool catalogs or configuration-driven deployments, store your schemas in a separate JSON file:

```python
import json
import needle

# tools.json contains an array of schemas

with open("tools.json") as f:
    schema = json.load(f)

agent = needle.Needle(tools=schema)
print(agent.run("dim the living room lights to 30"))

```

This approach keeps your Python code clean while allowing non-developers to modify tool definitions.

### Combining Decorator-Generated and Manual Schemas

You can mix automatically generated schemas with hand-written ones by accessing the `_needle_tool` attribute that the decorator attaches to functions. As implemented in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) at lines 62-65, the `@needle.tool` decorator stores the output of `build_schema(fn)` in `fn._needle_tool`:

```python
@needle.tool
def send_money(amount: float, to: str):
    """Send money to a user."""
    return {"sent": amount, "to": to}

# Hand-written schema for external service

extra = [{
    "name": "set_thermostat",
    "description": "Set the thermostat temperature.",
    "parameters": {
        "type": "object",
        "properties": {
            "temperature": {"type": "integer"},
            "mode": {"type": "string", "enum": ["heat", "cool", "auto"]},
        },
        "required": ["temperature"],
    },
}]

# Merge automatic and manual schemas

all_tools = [send_money._needle_tool] + extra
agent = needle.Needle(tools=all_tools)

```

## Key Implementation Details

The core logic resides in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), where the `build_schema` function (lines 10-41) inspects function signatures and docstrings to generate compliant JSON. When you manually define tool schemas as JSON in Needle 2, you bypass this introspection but must ensure your JSON conforms to the exact structure expected by the agent's validation logic.

The `parameters` object follows standard JSON Schema draft specifications, allowing you to define complex nested objects, arrays, and validation constraints. The agent uses these schemas for both LLM grammar generation at inference time and runtime parameter validation before tool execution.

## Summary

- Manually define tool schemas as JSON in Needle 2 by providing a list of dictionaries with `name`, `description`, and `parameters` keys to `needle.Needle(tools=...)`.
- The `parameters` field must be a valid JSON Schema object with `type: "object"`, a `properties` map, and a `required` array listing mandatory arguments.
- Store schemas in external JSON files for dynamic loading or mix manual schemas with decorator-generated ones using the `_needle_tool` attribute.
- The source implementation in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) ensures that manual and automatic schemas follow identical validation and grammar-generation pipelines.

## Frequently Asked Questions

### What is the exact JSON structure required for Needle 2 tool schemas?

The schema must be a dictionary containing `name` (string), `description` (string), and `parameters` (object). The `parameters` object must specify `type: "object"`, include a `properties` dictionary mapping argument names to their schema definitions, and optionally list required fields in a `required` array. This structure mirrors the output of `build_schema` in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).

### Can I use JSON Schema validation keywords like `minimum` and `pattern` in hand-written schemas?

Yes. Since the `parameters` field accepts standard JSON Schema, you can use any validation keywords supported by the specification, including `minimum`, `maximum`, `pattern`, `minLength`, `maxLength`, and `enum`. These constraints are respected during both LLM grammar generation and runtime parameter validation.

### How do I combine Python functions decorated with `@needle.tool` and external JSON schemas?

Access the automatically generated schema via the `_needle_tool` attribute attached to decorated functions, then concatenate it with your manual schema list. The decorator stores the result of `build_schema(fn)` at `fn._needle_tool` (lines 62-65 of [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)), allowing seamless integration of both approaches.

### Where can I find the official example of a hand-written tool schema?

The canonical example appears in [`doc/apis.md`](https://github.com/cactus-compute/needle/blob/main/doc/apis.md) (lines 51-68) within the cactus-compute/needle repository. This documentation demonstrates the complete JSON structure for manual tool definitions and explains how the `Needle` class consumes these schemas.