JuliusBrussee/caveman · error
no named tools found
Error message
no named tools found
What it means
extractTools iterates the recognized list and keeps only entries that yield a non-empty tool name (via "name" or nested "function.name"). If none survive — the list is empty or every entry lacks a name — it reports 'no named tools found' rather than returning an empty catalog.
Solutions
- Verify each tool entry has a "name" (or "function.name" for OpenAI shape) and fix typos
- Check the upstream server actually exposes tools — connect and list tools before exporting
- Lint a non-empty catalog: `{"tools":[{"name":"search","inputSchema":{...}}]}`
- Inspect the input with `jq '.tools | length'` to confirm tools exist
Example fix
// before
{"tools": [{"description": "search the web"}]}
// after
{"tools": [{"name": "web_search", "description": "search the web", "inputSchema": {"type": "object", "properties": {}}}]}} Defensive patterns
Strategy: validation
Validate before calling
const tools = JSON.parse(input).tools ?? []
const named = tools.filter(t => typeof t?.name === 'string' && t.name.length > 0)
if (named.length === 0) throw new Error('catalog has no named tools') Type guard
function hasNamedTools(catalog: unknown): boolean {
const list = Array.isArray(catalog) ? catalog
: (catalog as any)?.tools ?? (catalog as any)?.functions ?? []
return list.some((t: any) => typeof t?.name === 'string' && t.name) ||
list.some((t: any) => typeof t?.function?.name === 'string' && t.function.name)
} Try / catch
tools, err := extractTools(input)
if err != nil && err.Error() == "no named tools found" {
return fmt.Errorf("catalog empty or tools missing 'name' field; check upstream server")
} Prevention
- Verify the upstream server exposes tools before exporting its catalog
- Use the exact 'name' (MCP) or 'function.name' (OpenAI) key in hand-written entries
- Check tools length with jq before linting
When it happens
Trigger: Passing `{"tools":[]}`, an array of objects without "name" fields, or OpenAI entries missing the nested "function" object; also a single tool object whose name field is empty or misspelled (e.g. "tool_name").
Common situations: Exporting tools from a server that returned zero tools (all filtered or disabled); hand-written catalogs with a typo in the name key; responses truncated so tool objects lost their fields.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- assembly slot content is not JSON-serializable
- cave_output_schema_mismatch
- device authorization failed
- duplicate object key
- eval evidence line must be a JSON object
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/1cfb9d8fc647576e.
Report an issue: GitHub.
Appendix: source
Thrown at shrink/shrink.go:281
obj, ok := item.(map[string]any)
if !ok {
continue
}
// OpenAI nests under "function".
toolObj := obj
if fn, ok := obj["function"].(map[string]any); ok {
toolObj = fn
}
name, _ := toolObj["name"].(string)
if name == "" {
continue
}
raw, _ := json.Marshal(obj)
schema := schemaOf(toolObj)
entries = append(entries, toolEntry{name: name, raw: raw, schema: schema})
}
if len(entries) == 0 {
return nil, fmt.Errorf("no named tools found")
}
return entries, nil
}
// schemaOf returns a tool's parameter schema, under either "inputSchema" (MCP) or
// "parameters" (OpenAI).
func schemaOf(tool map[string]any) map[string]any {
if s, ok := tool["inputSchema"].(map[string]any); ok {
return s
}
if s, ok := tool["parameters"].(map[string]any); ok {
return s
}
return nil
}
// profileOf extracts param names, per-param enums, and the required list.
func profileOf(schema map[string]any) ToolProfile {View on GitHub (pinned to 3ee70a1026)