tool-schema 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres
5
+ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] - 2026-06-01
8
+
9
+ ### Added
10
+
11
+ - Initial release.
12
+ - `toToolSchema(schema, { target })` converts any JSON Schema into a provider
13
+ valid schema for `openai`, `openai-strict`, `anthropic`, `gemini`,
14
+ `gemini-jsonschema` and `mcp`.
15
+ - `toTool(def, { target })` builds a full provider shaped tool / function
16
+ declaration (`function`, `input_schema`, `parameters`, `inputSchema`),
17
+ including `strict` and MCP `annotations`.
18
+ - `lintToolSchema(schema, { target })` reports what would change without applying.
19
+ - `tool-schema` CLI: convert a schema file or stdin for any target.
20
+ - Zero runtime dependencies.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sebastian Legarraga
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # tool-schema
2
+
3
+ [![npm version](https://img.shields.io/npm/v/tool-schema.svg)](https://www.npmjs.com/package/tool-schema)
4
+ [![CI](https://github.com/slegarraga/tool-schema/actions/workflows/ci.yml/badge.svg)](https://github.com/slegarraga/tool-schema/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/tool-schema.svg)](./LICENSE)
6
+ [![zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](./package.json)
7
+
8
+ One JSON Schema in, a valid tool / function calling schema out, for **OpenAI**, **Anthropic**, **Gemini** and **MCP**. Zero dependencies.
9
+
10
+ Every provider accepts a slightly different subset of JSON Schema for tool calling, and the differences are exactly the kind that fail at runtime with a `400 invalid schema`:
11
+
12
+ - **OpenAI** strict mode demands `additionalProperties: false` on every object and every property listed in `required`, and rejects `allOf`, `not` and `if/then/else`.
13
+ - **Gemini** does not understand `$ref`, `oneOf`, `allOf` or `additionalProperties`, and expresses nullability as `nullable: true` instead of `type: ["string", "null"]`.
14
+ - **Anthropic** and **MCP** are permissive but still require an object at the root.
15
+
16
+ `tool-schema` knows these rules so you do not have to. Write your schema once, target any provider.
17
+
18
+ ## Install
19
+
20
+ ```sh
21
+ npm install tool-schema
22
+ ```
23
+
24
+ Requires Node 18+. Ships ESM and CommonJS with full TypeScript types.
25
+
26
+ ## Quick start
27
+
28
+ ```ts
29
+ import { toTool } from 'tool-schema';
30
+
31
+ const schema = {
32
+ type: 'object',
33
+ properties: {
34
+ city: { type: 'string', description: 'City name' },
35
+ units: { type: 'string', enum: ['c', 'f'] }, // optional
36
+ },
37
+ required: ['city'],
38
+ };
39
+
40
+ // OpenAI (Chat Completions) with Structured Outputs
41
+ const { tool } = toTool({ name: 'get_weather', description: 'Get the weather', schema }, { target: 'openai-strict' });
42
+ // tool -> { type: 'function', function: { name, description, parameters, strict: true } }
43
+ // `units` becomes required and nullable, additionalProperties:false is added everywhere.
44
+ ```
45
+
46
+ The same definition, four providers:
47
+
48
+ ```ts
49
+ toTool(def, { target: 'openai' }); // { type: 'function', function: { ... } }
50
+ toTool(def, { target: 'anthropic' }); // { name, description, input_schema }
51
+ toTool(def, { target: 'gemini' }); // { name, description, parameters }
52
+ toTool(def, { target: 'mcp' }); // { name, description, inputSchema, annotations? }
53
+ ```
54
+
55
+ ## Convert just the schema
56
+
57
+ When you already build the tool envelope yourself and only need a provider valid
58
+ parameter schema, use `toToolSchema`:
59
+
60
+ ```ts
61
+ import { toToolSchema } from 'tool-schema';
62
+
63
+ const { schema, warnings, lossy } = toToolSchema(mySchema, { target: 'gemini' });
64
+
65
+ // schema -> the Gemini valid schema ($ref inlined, oneOf stripped, nullable applied)
66
+ // warnings -> every adjustment made, with a JSON Pointer path and a stable code
67
+ // lossy -> true if any information had to be dropped
68
+ ```
69
+
70
+ ## Works with Zod
71
+
72
+ Zod 4 emits JSON Schema natively, so there is nothing extra to install:
73
+
74
+ ```ts
75
+ import { z } from 'zod';
76
+ import { toTool } from 'tool-schema';
77
+
78
+ const schema = z.toJSONSchema(z.object({ city: z.string(), units: z.enum(['c', 'f']).optional() }));
79
+
80
+ const { tool } = toTool({ name: 'get_weather', schema }, { target: 'openai-strict' });
81
+ ```
82
+
83
+ ## Lint without transforming
84
+
85
+ Want to know whether a schema is already valid for a provider, for example in a
86
+ test or a CI check?
87
+
88
+ ```ts
89
+ import { lintToolSchema } from 'tool-schema';
90
+
91
+ const { ok, issues } = lintToolSchema(mySchema, { target: 'openai-strict' });
92
+ if (!ok) {
93
+ for (const issue of issues) console.warn(`${issue.path}: ${issue.message}`);
94
+ }
95
+ ```
96
+
97
+ ## Targets
98
+
99
+ | Target | Output key | What it does |
100
+ | ------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
101
+ | `openai` | `function.parameters` | Ensures an object root. Otherwise pass through. |
102
+ | `openai-strict` | `function.parameters` | Structured Outputs: `additionalProperties:false`, all required, optionals nullable, unsupported keywords stripped, `allOf` merged. |
103
+ | `anthropic` | `input_schema` | Permissive. Ensures an object root. |
104
+ | `gemini` | `parameters` | OpenAPI subset: inlines `$ref`, strips `oneOf`/`allOf`/`additionalProperties`, `nullable: true`, string enums. |
105
+ | `gemini-jsonschema` | `parametersJsonSchema` | Gemini's richer route. Keeps `$ref` and more. |
106
+ | `mcp` | `inputSchema` | Most permissive. Ensures an object root. Supports `annotations`. |
107
+
108
+ ## Provider rules at a glance
109
+
110
+ | Constraint | openai | openai-strict | anthropic | gemini | mcp |
111
+ | ------------------------------------ | -------------- | ------------------------ | -------------- | ---------------- | ---- |
112
+ | Root must be object | yes | yes | yes | yes | yes |
113
+ | `additionalProperties: false` forced | no | yes (every object) | no | removed | no |
114
+ | All properties required | no | yes (optionals nullable) | no | no | no |
115
+ | `$ref` / `$defs` | keep | keep | keep | inlined | keep |
116
+ | `oneOf` / `allOf` / `not` | keep | stripped / merged | keep | stripped | keep |
117
+ | Nullability | `["t","null"]` | `["t","null"]` | `["t","null"]` | `nullable: true` | any |
118
+
119
+ ## CLI
120
+
121
+ ```sh
122
+ # Convert a schema file for a target
123
+ npx tool-schema schema.json --target openai-strict
124
+
125
+ # Pipe a schema and wrap it as a full tool definition
126
+ cat schema.json | npx tool-schema --target gemini --tool get_weather --description "Get the weather"
127
+ ```
128
+
129
+ The converted JSON goes to stdout. Warnings go to stderr, so the output is always
130
+ safe to pipe into another tool. Run `npx tool-schema --help` for all options.
131
+
132
+ ## Warnings
133
+
134
+ Every conversion returns a list of `warnings`. Each one has a `path` (JSON Pointer
135
+ to the node), a stable `code`, and a human readable `message`. Codes include
136
+ `stripped-keyword`, `forced-required`, `forced-additional-properties`,
137
+ `inlined-ref`, `collapsed-nullable`, `enum-coerced`, `merged-allof`,
138
+ `unsupported-format`, `limit-exceeded` and `invalid-name`. `lossy` is `true`
139
+ whenever a keyword or constraint had to be dropped.
140
+
141
+ ## Why zero dependencies
142
+
143
+ This library is meant to sit deep in agent and tool pipelines. No transitive
144
+ dependencies means no supply chain surface, no version conflicts, and a tiny
145
+ install. It uses only the JSON Schema you pass in and the platform `structuredClone`.
146
+
147
+ ## License
148
+
149
+ MIT (c) Sebastian Legarraga. See [LICENSE](./LICENSE).