The portability problem

One tool.
Six incompatible formats.

Every AI framework invented its own way to define tools. Same function. Same parameters. Six different JSON schemas. Here's what that actually looks like.

Function: get_weather(city, unit) Returns: {"temp": 22, "city": "London"}

The same function, defined six ways

Each card below defines the exact same get_weather(city, unit) tool. Notice the highlighted differences -- every framework uses different key names for the same thing.

OpenAI Function Calling 15 lines
{
  "type": "function",
  "function": {
    "name": "get_weather",
    "description": "Get current weather for a city.",
    "parameters": {
      "type": "object",
      "properties": {
        "city": {"type": "string", "description": "The city name"},
        "unit": {"type": "string", "description": "Temperature unit (celsius or fahrenheit)", "default": "celsius"}
      },
      "required": ["city"]
    }
  }
}
Anthropic Tool Use 13 lines
{
  "name": "get_weather",
  "description": "Get current weather for a city.",
  "input_schema": {
    "type": "object",
    "properties": {
      "city": {"type": "string", "description": "The city name"},
      "unit": {"type": "string", "description": "Temperature unit (celsius or fahrenheit)", "default": "celsius"}
    },
    "required": ["city"]
  }
}
Google / Gemini 13 lines
{
  "name": "get_weather",
  "description": "Get current weather for a city.",
  "parameters": {
    "type": "OBJECT",
    "properties": {
      "city": {"type": "STRING", "description": "The city name"},
      "unit": {"type": "STRING", "description": "Temperature unit (celsius or fahrenheit)"}
    },
    "required": ["city"]
  }
}
MCP (Model Context Protocol) 13 lines
{
  "name": "get_weather",
  "description": "Get current weather for a city.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "city": {"type": "string", "description": "The city name"},
      "unit": {"type": "string", "description": "Temperature unit (celsius or fahrenheit)", "default": "celsius"}
    },
    "required": ["city"]
  }
}
LangChain 14 lines
from langchain.tools import StructuredTool
from pydantic import BaseModel, Field

class GetWeatherInput(BaseModel):
    city: str = Field(description="The city name")
    unit: str = Field(default="celsius", description="Temperature unit")

def get_weather(city: str, unit: str = "celsius") -> dict:
    return {"temp": 22, "unit": unit, "city": city}

weather_tool = StructuredTool.from_function(
    func=get_weather,
    name="get_weather",
    description="Get current weather for a city.",
    args_schema=GetWeatherInput,
)
CrewAI 10 lines
from crewai.tools import tool

@tool("get_weather")
def get_weather(city: str, unit: str = "celsius") -> dict:
    """Get current weather for a city.
    Args:
        city: The city name
        unit: Temperature unit (celsius or fahrenheit)
    """
    return {"temp": 22, "unit": unit, "city": city}
# Note: CrewAI tools only work within CrewAI
Total lines to define one function: 78 lines across 6 formats
parameters input_schema inputSchema args_schema

Four names for the same thing. "OBJECT" vs "object". Wrapped in "function" or not. None of this is your problem to solve.

agent-friend: define once, export everywhere

Write a normal Python function. Add one decorator. Get every format.

agent-friend 10 lines — all formats
from agent_friend import tool

@tool
def get_weather(city: str, unit: str = "celsius") -> dict:
    """Get current weather for a city.

    Args:
        city: The city name
        unit: Temperature unit (celsius or fahrenheit)
    """
    return {"temp": 22, "unit": unit, "city": city}

# One definition. Every format.
get_weather.to_openai()      # OpenAI function calling
get_weather.to_anthropic()   # Claude tool use
get_weather.to_google()      # Gemini function declarations
get_weather.to_mcp()         # Model Context Protocol
get_weather.to_json_schema() # Raw JSON Schema
to_openai()
{"type": "function", "function": {"name": ...}}
Ready for chat completions API
to_anthropic()
{"name": ..., "input_schema": {...}}
Ready for messages API
to_google()
{"name": ..., "parameters": {"type": "OBJECT"}}
Ready for Gemini API
to_mcp()
{"name": ..., "inputSchema": {...}}
Ready for MCP servers

Got 50 tools? Export them all at once.

The Toolkit class bundles multiple tools and exports them as a list for any framework.

Toolkit batch export
from agent_friend import tool, Toolkit

@tool
def get_weather(city: str) -> dict: ...

@tool
def send_email(to: str, body: str) -> bool: ...

@tool
def search_docs(query: str, limit: int = 10) -> list: ...

kit = Toolkit([get_weather, send_email, search_docs])
kit.to_openai()    # list of 3 OpenAI tool defs
kit.to_anthropic() # list of 3 Anthropic tool defs
kit.to_mcp()        # list of 3 MCP tool defs

Built for people who use more than one LLM

Zero lock-in

Switch from OpenAI to Anthropic to Gemini without rewriting your tool definitions. Your logic stays the same. Only the export changes.

Zero dependencies

No pydantic. No langchain. No framework imports. Just Python's standard library. The @tool decorator adds nothing you wouldn't write yourself.

51 built-in tools

File I/O, HTTP, JSON, CSV, crypto, templates, metrics, and more. All tested. All exportable. Use them directly or as reference implementations.

MCP server included

Run python mcp_server.py and expose all 314 tools via the Model Context Protocol. Connect it to Claude Desktop or any MCP client.

2,474 tests

Every tool. Every export format. Every edge case. The test suite is the specification. If a format changes, the tests catch it first.

MIT licensed

Use it however you want. Fork it. Vendor it. Embed it. The code is on GitHub and it's yours.

30 seconds to your first export

$ pip install "git+https://github.com/0-co/agent-friend.git[all]"

v0.49.0 · Python 3.8+ · Zero external dependencies · MIT license