{"id":50578498,"url":"https://github.com/slegarraga/tool-schema","last_synced_at":"2026-06-05T00:02:58.650Z","repository":{"id":361886233,"uuid":"1256158689","full_name":"slegarraga/tool-schema","owner":"slegarraga","description":"Convert any JSON Schema into a valid tool / function calling schema for OpenAI, Anthropic, Gemini and MCP. Zero dependencies.","archived":false,"fork":false,"pushed_at":"2026-06-01T17:54:23.000Z","size":95,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-01T18:14:40.066Z","etag":null,"topics":["ai-agents","anthropic","function-calling","gemini","json-schema","llm","mcp","openai","structured-outputs","tool-use"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/slegarraga.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-01T14:13:15.000Z","updated_at":"2026-06-01T17:54:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/slegarraga/tool-schema","commit_stats":null,"previous_names":["slegarraga/tool-schema"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/slegarraga/tool-schema","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slegarraga%2Ftool-schema","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slegarraga%2Ftool-schema/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slegarraga%2Ftool-schema/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slegarraga%2Ftool-schema/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/slegarraga","download_url":"https://codeload.github.com/slegarraga/tool-schema/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slegarraga%2Ftool-schema/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33924837,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","anthropic","function-calling","gemini","json-schema","llm","mcp","openai","structured-outputs","tool-use"],"created_at":"2026-06-05T00:02:49.558Z","updated_at":"2026-06-05T00:02:53.972Z","avatar_url":"https://github.com/slegarraga.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tool-schema\n\n[![npm version](https://img.shields.io/npm/v/tool-schema.svg)](https://www.npmjs.com/package/tool-schema)\n[![npm downloads](https://img.shields.io/npm/dm/tool-schema.svg)](https://www.npmjs.com/package/tool-schema)\n[![CI](https://github.com/slegarraga/tool-schema/actions/workflows/ci.yml/badge.svg)](https://github.com/slegarraga/tool-schema/actions/workflows/ci.yml)\n[![license](https://img.shields.io/npm/l/tool-schema.svg)](./LICENSE)\n[![zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](./package.json)\n\nOne JSON Schema in, a valid tool / function calling schema out, for **OpenAI**, **Anthropic**, **Gemini** and **MCP**. Zero dependencies.\n\nEvery 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`:\n\n- **OpenAI** strict mode demands `additionalProperties: false` on every object and every property listed in `required`, and rejects `allOf`, `not` and `if/then/else`.\n- **Gemini** does not understand `$ref`, `oneOf`, `allOf` or `additionalProperties`, and expresses nullability as `nullable: true` instead of `type: [\"string\", \"null\"]`.\n- **Anthropic** and **MCP** are permissive but still require an object at the root.\n\n`tool-schema` knows these rules so you do not have to. Write your schema once, target any provider.\n\n## Install\n\n```sh\nnpm install tool-schema\n```\n\nRequires Node 18+. Ships ESM and CommonJS with full TypeScript types.\n\n## Quick start\n\n```ts\nimport { toTool } from 'tool-schema';\n\nconst schema = {\n  type: 'object',\n  properties: {\n    city: { type: 'string', description: 'City name' },\n    units: { type: 'string', enum: ['c', 'f'] }, // optional\n  },\n  required: ['city'],\n};\n\n// OpenAI (Chat Completions) with Structured Outputs\nconst { tool } = toTool({ name: 'get_weather', description: 'Get the weather', schema }, { target: 'openai-strict' });\n// tool -\u003e { type: 'function', function: { name, description, parameters, strict: true } }\n// `units` becomes required and nullable, additionalProperties:false is added everywhere.\n```\n\nThe same definition, four providers:\n\n```ts\ntoTool(def, { target: 'openai' }); // { type: 'function', function: { ... } }\ntoTool(def, { target: 'anthropic' }); // { name, description, input_schema }\ntoTool(def, { target: 'gemini' }); // { name, description, parameters }\ntoTool(def, { target: 'mcp' }); // { name, description, inputSchema, annotations? }\n```\n\n## Convert just the schema\n\nWhen you already build the tool envelope yourself and only need a provider valid\nparameter schema, use `toToolSchema`:\n\n```ts\nimport { toToolSchema } from 'tool-schema';\n\nconst { schema, warnings, lossy } = toToolSchema(mySchema, { target: 'gemini' });\n\n// schema  -\u003e the Gemini valid schema ($ref inlined, oneOf stripped, nullable applied)\n// warnings -\u003e every adjustment made, with a JSON Pointer path and a stable code\n// lossy   -\u003e true if any information had to be dropped\n```\n\n## Works with Zod\n\nZod 4 emits JSON Schema natively, so there is nothing extra to install:\n\n```ts\nimport { z } from 'zod';\nimport { toTool } from 'tool-schema';\n\nconst schema = z.toJSONSchema(z.object({ city: z.string(), units: z.enum(['c', 'f']).optional() }));\n\nconst { tool } = toTool({ name: 'get_weather', schema }, { target: 'openai-strict' });\n```\n\n## Lint without transforming\n\nWant to know whether a schema is already valid for a provider, for example in a\ntest or a CI check?\n\n```ts\nimport { lintToolSchema } from 'tool-schema';\n\nconst { ok, issues } = lintToolSchema(mySchema, { target: 'openai-strict' });\nif (!ok) {\n  for (const issue of issues) console.warn(`${issue.path}: ${issue.message}`);\n}\n```\n\n## Targets\n\n| Target              | Output key             | What it does                                                                                                                       |\n| ------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |\n| `openai`            | `function.parameters`  | Ensures an object root. Otherwise pass through.                                                                                    |\n| `openai-strict`     | `function.parameters`  | Structured Outputs: `additionalProperties:false`, all required, optionals nullable, unsupported keywords stripped, `allOf` merged. |\n| `anthropic`         | `input_schema`         | Permissive. Ensures an object root.                                                                                                |\n| `gemini`            | `parameters`           | OpenAPI subset: inlines `$ref`, strips `oneOf`/`allOf`/`additionalProperties`, `nullable: true`, string enums.                     |\n| `gemini-jsonschema` | `parametersJsonSchema` | Gemini's richer route. Keeps `$ref` and more.                                                                                      |\n| `mcp`               | `inputSchema`          | Most permissive. Ensures an object root. Supports `annotations`.                                                                   |\n\n## Provider rules at a glance\n\n| Constraint                           | openai         | openai-strict            | anthropic      | gemini           | mcp  |\n| ------------------------------------ | -------------- | ------------------------ | -------------- | ---------------- | ---- |\n| Root must be object                  | yes            | yes                      | yes            | yes              | yes  |\n| `additionalProperties: false` forced | no             | yes (every object)       | no             | removed          | no   |\n| All properties required              | no             | yes (optionals nullable) | no             | no               | no   |\n| `$ref` / `$defs`                     | keep           | keep                     | keep           | inlined          | keep |\n| `oneOf` / `allOf` / `not`            | keep           | stripped / merged        | keep           | stripped         | keep |\n| Nullability                          | `[\"t\",\"null\"]` | `[\"t\",\"null\"]`           | `[\"t\",\"null\"]` | `nullable: true` | any  |\n\n## CLI\n\n```sh\n# Convert a schema file for a target\nnpx tool-schema schema.json --target openai-strict\n\n# Pipe a schema and wrap it as a full tool definition\ncat schema.json | npx tool-schema --target gemini --tool get_weather --description \"Get the weather\"\n```\n\nThe converted JSON goes to stdout. Warnings go to stderr, so the output is always\nsafe to pipe into another tool. Run `npx tool-schema --help` for all options.\n\n## Warnings\n\nEvery conversion returns a list of `warnings`. Each one has a `path` (JSON Pointer\nto the node), a stable `code`, and a human readable `message`. Codes include\n`stripped-keyword`, `forced-required`, `forced-additional-properties`,\n`inlined-ref`, `collapsed-nullable`, `enum-coerced`, `merged-allof`,\n`unsupported-format`, `limit-exceeded` and `invalid-name`. `lossy` is `true`\nwhenever a keyword or constraint had to be dropped.\n\n## Why zero dependencies\n\nThis library is meant to sit deep in agent and tool pipelines. No transitive\ndependencies means no supply chain surface, no version conflicts, and a tiny\ninstall. It uses only the JSON Schema you pass in and the platform `structuredClone`.\n\n## Part of a set\n\n`tool-schema` pairs with [`llm-messages`](https://github.com/slegarraga/llm-messages),\nwhich converts your chat **conversations** across the same providers. Together\nthey let you write an agent once and run it on any LLM.\n\n## License\n\nMIT (c) Sebastian Legarraga. See [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslegarraga%2Ftool-schema","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fslegarraga%2Ftool-schema","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslegarraga%2Ftool-schema/lists"}