Tool: it hands the framework an argument schema and a coroutine, and execution stays in Charter.
That means tools stay built once. The credential lookup happens below every adapter, so serving many users works unchanged whichever one you use — or none.
Every adapter publishes
<pack>_<tool>. Both factories require a pack for that reason, so a tool built by one always has a name an assembler can use. A Tool constructed directly does not — that case is for something you only ever ainvoke yourself — and publishing one is refused here rather than given a bare name that a +stripe filter or a log grep would silently miss. The naming rules are the argument.OpenAI
openai_tools.py
Tool.to_json_schema produces — so this needs no extra. The same output works anywhere the OpenAI tool format is accepted.
LangChain
langchain_tools.py
StructuredTool per tool, whose args_schema is the Charter LLM view (Tool.llm_schema) and whose coroutine is Tool.ainvoke. LangChain supplies the calling convention; it does not touch serialization, casing, or field policy.
Validation failures surface the way LangChain expects, so a model that sends a malformed argument gets a message it can act on rather than a stack trace. See tool validation and error handling.
This one needs the extra:
Passing the host’s context
A tool with aFromContext field needs the
host to say whom each call is for, and every adapter carries that through, from
wherever the framework already keeps it. The model never supplies it: the field
is not in the schema any adapter publishes.
OpenAI. The run’s context goes to every call it makes:
CharterMiddleware
reads the agent’s own context, the one agent.ainvoke(..., context=...) is
given, on every tool call. One agent serves every user, and a dataclass, a
TypedDict or a pydantic model all work:
to_langchain(tool, context={...}) and
to_langchain_tools(tools, context={...}) bind one mapping to every call; wrap
per request when it varies by user.
MCP. build_server(tools, context=...) takes a mapping, for a stdio server,
which is one process for one user, or a function of the MCP request context,
sync or async, called for every tool call, for a server that learns the user
from the request:
ContextError, whose message tells the model
the application has to fix it and that a different call will not help.
Sending schemas on demand
AToolSession sends the schemas of the tools the model has reached for, plus a ToolSearch that loads the rest, so Gmail and Stripe together cost 924 tokens on the first turn instead of 41,361.
Its tool list grows, which means the loop has to rebuild that list every turn. Charter ships the loop rather than asking you to remember:
Tool.ainvoke exactly as a statically bound tool would be. A loaded tool is the same fully typed tool it would have been.
to_openai_tools and to_langchain_tools both accept a session too, if you own your loop and want to drive the projection yourself. See run and CharterMiddleware.
Serving over the Model Context Protocol is a compile target rather than an adapter, and has its own page.
Related
Tool— the object every adapter projectsTool.llm_schemaandTool.to_json_schema— the two views an adapter hands overTool.ainvoke— the coroutine they wrap