Architecture¶
Layer model¶
Layer 1: SCHEMA GENERATION ← toolschema (this project)
Layer 2: VALIDATION ← toolschema.validate() (thin) or Pydantic
Layer 3: ORCHESTRATION ← LangChain, Pydantic AI, OpenAI Agents, etc.
Layer 4: TRANSPORT ← FastMCP, HTTP APIs, stdio MCP
toolschema owns Layer 1 only.
Data flow¶
┌─────────────────────────────────────┐
│ Python function │
│ - type hints │
│ - defaults │
│ - docstring / @tool metadata │
└──────────────┬──────────────────────┘
│ schema(fn)
▼
┌─────────────────────────────────────┐
│ ToolDefinition (IR) │
│ - name, description │
│ - parameters (JSON Schema 2020-12) │
│ - output (optional) │
└──────────────┬──────────────────────┘
│
┌─────────┼─────────┬─────────────┐
▼ ▼ ▼ ▼
to_openai to_mcp to_anthropic to_gemini
│ │ │ │
▼ ▼ ▼ ▼
integrations/ (optional glue to frameworks)
Core modules¶
| Module | Role |
|---|---|
_decorator.py |
@tool decorator, ToolMeta |
_introspect.py |
schema() — signature → ToolDefinition |
_types.py |
type_to_schema() type mapping |
_fields.py |
Field, Annotated metadata extraction |
_ir.py |
ToolDefinition dataclass + adapter methods |
_validate.py |
validate_arguments() |
_standard.py |
Standard Schema protocol |
_schema_utils.py |
Strip $schema for provider export |
Adapter modules¶
| Module | Output |
|---|---|
adapters/openai.py |
OpenAI function tool |
adapters/anthropic.py |
Anthropic input_schema |
adapters/mcp.py |
MCP inputSchema / outputSchema |
adapters/gemini.py |
Gemini parameters |
adapters/_inline_refs.py |
$ref flattening |
Rule: Adapters read ToolDefinition only. Never call type_to_schema() inside an adapter.
Integration modules¶
| Module | Framework |
|---|---|
integrations/fastmcp.py |
FastMCP register_tool |
integrations/langchain.py |
LangChain StructuredTool |
integrations/openai_agents.py |
OpenAI Agents FunctionTool |
integrations/pydantic_ai.py |
Pydantic AI Tool.from_schema |
Integrations are optional extras — not imported by core.
Design principles¶
- Function-first — plain Python, Pydantic optional
- Zero framework lock-in in core package
- Single IR — one
ToolDefinition, many adapters - Pre-PEP aligned —
schema(fn),@toolmatch proposed stdlib - MCP-safe defaults — inline
$refby default - Testable — golden snapshots per adapter
JSON Schema dialect¶
Internal canonical dialect: JSON Schema 2020-12
JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema"
Provider exports strip $schema where providers reject unknown keys.
Testing strategy¶
| Layer | Tests |
|---|---|
| Type mapping | test_core.py, test_types_extended.py |
| Adapters | test_adapters.py + tests/snapshots/ |
| Integrations | test_integrations.py, test_parity.py |
| End-to-end | test_complex_e2e.py, test_deep_agents.py |
| MCP stdio | test_mcp_smoke.py |
| CLI | test_cli.py |
| Scaffold | test_init.py |
What we deliberately don't build¶
- Agent memory / planning
- MCP wire protocol (use FastMCP)
- LLM API clients
- Full JSON Schema validation engine (use Pydantic or jsonschema for that)