Skip to main content

toolschema

PyPI version Python CI License: MIT

Function → JSON Schema, once, everywhere.

Python's answer to the gap TypeScript solved with Zod + Standard Schema. Write a typed function. Export one schema. Use it in OpenAI, Anthropic, Gemini, MCP, LangChain, FastMCP, and Pydantic AI — without rewriting.

Every agent framework generates tool JSON differently. FastMCP is MCP-only. LangChain infers its own schema. OpenAI strict mode wants every field required. Claude Desktop breaks on $ref. toolschema is Layer 1 only: introspect a function once, adapt at the edge.

Install

pip install toolschema

Extras:

pip install toolschema[fastmcp]        # FastMCP
pip install toolschema[langchain]      # LangChain StructuredTool
pip install toolschema[openai-agents]  # OpenAI Agents SDK
pip install toolschema[pydantic-ai]    # Pydantic AI Tool.from_schema
pip install toolschema[all]            # all integrations

Requires Python 3.10+. Core depends on stdlib only (typing_extensions on 3.10).

Documentation: https://toolschema.readthedocs.io

Usage

Core

from typing import Annotated
from toolschema import tool, schema, Field

@tool
def get_weather(
    city: Annotated[str, Field(description="City name")],
    units: str = "celsius",
) -> dict:
    """Get current weather for a city."""
    return {"city": city, "temp": 22, "units": units}

t = schema(get_weather)
t.to_openai()
t.to_mcp()
t.to_anthropic()

Works without @tool on any typed function with a docstring:

def add(a: int, b: int = 1) -> int:
    """Add two integers."""
    return a + b

t = schema(add)

FastMCP

from fastmcp import FastMCP
from toolschema import schema
from toolschema.integrations.fastmcp import register_tool
from myapp.tools import greet, add

mcp = FastMCP("my-server")
register_tool(mcp, schema(greet), greet)
register_tool(mcp, schema(add), add)

mcp.run()

LangChain

from toolschema import schema
from toolschema.integrations.langchain import from_toolschema
from myapp.tools import search

definition = schema(search)
tool = from_toolschema(definition, search)
result = tool.invoke({"query": "laptop", "limit": 5})

OpenAI Agents

from toolschema import schema
from toolschema.integrations.openai_agents import to_agents_function_tool
from myapp.tools import add

definition = schema(add)
agents_tool = to_agents_function_tool(definition, add)

Pydantic AI

from pydantic_ai import Agent
from toolschema import schema
from toolschema.integrations.pydantic_ai import from_toolschema
from myapp.tools import add

definition = schema(add)
tool = from_toolschema(definition, add)
agent = Agent("openai:gpt-4o", tools=[tool])

CLI

toolschema inspect myapp.tools:search --format mcp
toolschema inspect myapp.tools:search --format openai,mcp,anthropic
toolschema diff myapp.tools:search --targets openai,mcp
toolschema export myapp.tools
toolschema init my-mcp-server

API

schema(fn) -> ToolDefinition

Build the canonical tool IR from a function signature, type hints, defaults, and docstring.

fn

Required
Type: Callable

Any typed callable, or @tool-decorated function.

from toolschema import schema

t = schema(my_function)
t.name
t.description
t.parameters   # JSON Schema 2020-12 object
t.output       # return type schema, or None

@tool

Optional decorator. Overrides name or description; does not change call semantics.

@tool(name="web_search", description="Search the web.")
def search(query: str) -> list[dict]: ...

Field(...)

Metadata for Annotated parameters. Maps to JSON Schema constraints.

from typing import Annotated
from toolschema import Field

city: Annotated[str, Field(description="City name", min_length=1)]

Plain string in Annotated sets description only: Annotated[str, "City name"].


ToolDefinition.to_openai(*, strict=False)

OpenAI function-calling payload: {"type": "function", "function": {...}}.

strict

Type: boolean
Default: false

When true, sets additionalProperties: false and lists every property in required.


ToolDefinition.to_anthropic()

Anthropic Messages API shape: {"name", "description", "input_schema"}.

Numeric and string constraints (minLength, pattern, etc.) are folded into property description text.


ToolDefinition.to_mcp(*, inline_refs=True)

MCP tools/list shape: name, description, inputSchema, optional outputSchema.

inline_refs

Type: boolean
Default: true

When true, flatten $ref / $defs before return. Required for Claude Desktop and VS Code Copilot.


ToolDefinition.to_gemini()

Google Gemini FunctionDeclaration shape. JSON Schema type values are uppercased (STRING, INTEGER, …). Parameters only; no output schema in v1.0.


ToolDefinition.validate(args) -> ValidationResult

Thin argument check against parameters. Returns ValidationSuccess with defaults applied, or ValidationFailure with ValidationIssue list.

from toolschema import ValidationSuccess

result = t.validate({"city": "London"})
if isinstance(result, ValidationSuccess):
    get_weather(**result.value)

register_tool(mcp, tool, fn) (FastMCP)

Register a pre-built ToolDefinition on a FastMCP server without regenerating schema inside FastMCP.

mcp

Required
Type: fastmcp.FastMCP

tool

Required
Type: ToolDefinition

fn

Required
Type: Callable


from_toolschema(tool, fn) (LangChain)

Build a StructuredTool with infer_schema=False and args_schema=tool.parameters.


to_agents_function_tool(tool, fn, *, strict=False) (OpenAI Agents)

Build an OpenAI Agents SDK FunctionTool with on_invoke_tool wired to fn.

Also: function_tool_kwargs(), to_openai_agent_tool(), invoke_agents_tool(), invoke_agents_tool_sync().


from_toolschema(tool, fn) (Pydantic AI)

Build a Pydantic AI Tool via Tool.from_schema() and the pre-built JSON Schema.

Also: to_pydantic_ai_tool(), prepare_toolset().


toolschema init NAME [--path DIR]

Scaffold an MCP server project from the packaged template. Runs slugify_package_name() on the directory name for the Python package.

Canonical schema

Internal dialect: JSON Schema 2020-12. Parameters object always has additionalProperties: false.

{
  "name": "add",
  "description": "Add two integers.",
  "parameters": {
    "type": "object",
    "properties": {
      "a": { "type": "integer" },
      "b": { "type": "integer", "default": 1 }
    },
    "required": ["a"],
    "additionalProperties": false
  }
}
Python JSON Schema
str, int, float, bool string, integer, number, boolean
T | None anyOf: [schema(T), {"type": "null"}]
list[T], dict[str, T] array, object + additionalProperties
Literal, Enum enum
Annotated[T, Field(...)] constraints on property
TypedDict, @dataclass, Union, tuple object, anyOf, prefixItems
default value "default" key; omitted from required
return annotation output / MCP outputSchema

All adapters read from ToolDefinition only. Schema is not generated twice inside an adapter.

Why not framework @tool alone?

Function              FastMCP           LangChain          OpenAI
--------              -------           ---------          ------
@mcp.tool()     →     MCP JSON          rewrite            rewrite
LC @tool        →     rewrite           LC schema          rewrite
hand-written    →     drift             drift              drift
  • Pre-PEP inspect.tool_schema: proposed stdlib fix, not shipped
  • FastMCP @mcp.tool(): MCP transport + inference, not portable
  • LangChain StructuredTool.from_function: infers schema per call site
  • Pydantic model_json_schema(): domain models, not function-tool IR

Comparison

Solution OpenAI Anthropic MCP LangChain FastMCP Lock-in free
Framework @tool partial partial partial partial partial no
Hand-written JSON Schema manual manual manual manual manual yes
toolschema yes yes yes yes yes yes

Security

toolschema export and toolschema inspect import user-specified modules. Only point them at code you trust.

validate() checks arguments against generated schema; it does not sandbox tool execution. Your agent framework still runs the callable.

Examples

Path Description
examples/01_basic.py @tool, schema(), adapter output
examples/02_mcp_server.py FastMCP stdio server + --check smoke test
examples/03_langchain.py LangChain from_toolschema + invoke
examples/04_multi_provider.py One function, all provider formats
examples/demo_tools.py Reusable tools module
examples/verify_package.py PyPI install verification
examples/deep_agents_demo.py Cross-framework integration smoke test

License

MIT. See LICENSE.

Community

Metadata

Release files for toolschema 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for toolschema 1.0.1
File Size Uploaded
toolschema-1.0.1.tar.gz 277.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for toolschema 1.0.1
File Interpreter ABI Platform
toolschema-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 307.0 kB

Release files / toolschema-1.0.1.tar.gz

Download URL toolschema-1.0.1.tar.gz
Size 277.0 kB
Tags Source
SHA-256 checksum
How to use checksums
df7c099e4fbc166aa36363c6d08f54fc1248575655b360203c5ae73c707b5dfd
BLAKE2b-256 checksum
How to use checksums
bfa9c3bac3a9498fbd3d16f400af8f2eb3db9c32372c19c053c13544212dee2d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / toolschema-1.0.1-py3-none-any.whl

Download URL toolschema-1.0.1-py3-none-any.whl
Size 30.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
50a9a7fba09a0da94183c9d3d9596062db82143657800da20b0f083d26a30b58
BLAKE2b-256 checksum
How to use checksums
173841b997f93fe50c9dfaf46efc3d71baa4a1ff78836d4b53d32a11294de249
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page