create_toolkit
Python · package toolnexus · SPEC §4 · python/src/toolnexus/toolkit.py
async def create_toolkit( mcp_config: str | dict[str, Any] | None = None, skills_dir: str | list[str] | None = None, skills: list[SkillDef] | None = None, skill_provider: SkillProvider | None = None, skills_filter: dict[str, bool] | None = None, skill_sample_limit: int = 0, extra_tools: list[Tool] | None = None, builtins: BuiltinsConfig | None = None, agents: list[Agent] | None = None, wait_for: Any | None = None, disable_tools: list[str] | None = None, disable_skills: list[str] | None = None, cancel: Any | None = None,) -> Toolkitcreate_toolkit is the other entry point: it gathers every tool source you point it at — MCP
servers, a skills directory or an in-memory skill list, built-in tools, remote A2A agents, and any
of your own Tools — and hands back one Toolkit with a single flat, deduplicated tool list. Pass
that Toolkit into client.run(prompt, toolkit) and the model sees one uniform surface no matter
how many sources fed it.
It is async because connecting MCP servers and globbing/reading skill files are I/O; the
returned Toolkit is also an async context manager, and await tk.close() tears every source
(MCP subprocesses/HTTP connections, served A2A handles) back down.
When to use it
Section titled “When to use it”- You have one or more MCP servers (
mcp.json, local stdio or remote streamable-HTTP) and want every tool they expose as a plainTool, without hand-writing the MCP client dance. - You have a
skills/directory (or a static/dynamic list ofSkillDefs) and want them exposed as oneskilltool with progressive disclosure, instead of dumping every skill’s full body into the system prompt. - You want built-in tools (shell, file read/write, …), your own
define_tool/http_toolfunctions, and remote A2A agents’ skills to show up in the same tool list as the above — so the model sees one surface regardless of where a tool actually lives. - You need to scope that surface down per audience — a per-skill allow/deny map (
skills_filter), a globaldisable_tools/disable_skillsdrop-list, orbuiltins=Falseto turn off the whole built-in source.
Why this and not the alternative
Section titled “Why this and not the alternative”The option surface
Section titled “The option surface”| Option | What it’s for | Depth |
|---|---|---|
mcp_config |
A path to mcp.json, or an already-parsed config dict — connects every server it names. |
load_mcp, parse_mcp_config |
skills_dir |
One or more directories to glob for **/SKILL.md; exposed as one skill tool. |
load_skills |
skills |
A static in-memory list of SkillDef — same skill tool, no filesystem. |
load_skills |
skill_provider |
A callable (sync or async) returning list[SkillDef] on demand — for a dynamic/remote skill catalog. |
load_skills |
skills_filter |
Per-skill allow/deny map, composed with disable_skills. |
load_skills |
skill_sample_limit |
Caps how many example skills are sampled into the catalog summary (0 = no cap). |
load_skills |
extra_tools |
Your own Tools (from define_tool / http_tool, or built by hand) merged straight in. |
Tool |
builtins |
True/False/None or a {"enabled": ...} / {"disabled": ...} dict — the whole built-in source is on by default. |
create_builtin_tools, select_builtins |
agents |
A list of agent(cardUrl) descriptors — each remote skill becomes an <agent>_<skill> tool (source "a2a"). |
agent |
wait_for |
Same §10 suspension hook as create_client — set here if this toolkit’s own tools (not just the client’s) can suspend. |
WaitFor |
disable_tools |
Drops tools by their final exposed name, across every source, after merging. | — |
disable_skills |
Drops skills by name from the skill catalog (composes with skills_filter). |
— |
cancel |
An asyncio.Event — set it to abort an in-flight MCP connect/load (SPEC §2 Gap 3). |
— |
Examples
Section titled “Examples”1. The smallest useful call — no sources, just built-ins
Section titled “1. The smallest useful call — no sources, just built-ins”import asyncio
from toolnexus import create_toolkit
async def main(): tk = await create_toolkit() try: names = sorted(t.name for t in tk.tools()) print("built-in tools:", names) assert len(names) > 0 # builtins are on by default finally: await tk.close()
asyncio.run(main())2. The realistic case — your own tool, a static skill, and a drop-list
Section titled “2. The realistic case — your own tool, a static skill, and a drop-list”from toolnexus import create_toolkit, define_toolfrom toolnexus.skill import SkillDefimport asyncio
async def main(): def add(a: float, b: float) -> str: """Add two numbers and return the sum.""" return str(a + b)
tk = await create_toolkit( builtins=False, extra_tools=[define_tool(add, name="add")], skills=[ SkillDef(name="keep", description="a skill worth keeping", content="k"), SkillDef(name="drop", description="a skill to hide from this agent", content="d"), ], disable_skills=["drop"], ) try: assert tk.get("add") is not None prompt = tk.skills_prompt() assert "keep" in prompt and "**drop**" not in prompt print("tools:", [t.name for t in tk.tools()]) print("skill catalog mentions 'keep':", "keep" in prompt, "| mentions 'drop':", "**drop**" in prompt) finally: await tk.close()
asyncio.run(main())3. The full surface — MCP servers + skills directory + tool-name drop-list
Section titled “3. The full surface — MCP servers + skills directory + tool-name drop-list”import asyncioimport jsonimport os
from toolnexus import create_toolkit
_HERE = os.path.dirname(os.path.abspath(__file__))_EXAMPLES = os.path.normpath(os.path.join(_HERE, "..", "..", "examples"))
async def main() -> None: tk = await create_toolkit( mcp_config=os.path.join(_EXAMPLES, "mcp.json"), skills_dir=os.path.join(_EXAMPLES, "skills"), skill_sample_limit=5, disable_tools=["everything_echo"], # drop one tool by its exposed name )
print("MCP status:", tk.mcp_status()) print("Tools:", [f"{t.name} ({t.source})" for t in tk.tools()]) print("\nSystem-prompt skill catalog:\n" + tk.skills_prompt())
print("\nOpenAI tool schema (first 2):") print(json.dumps(tk.to_openai()[:2], indent=2))
res = await tk.execute("skill", {"name": "hello-world"}) print("\nskill(hello-world) ->\n" + res.output)
await tk.close()
if __name__ == "__main__": asyncio.run(main())See also
Section titled “See also”create_client— The unified client: system prompt, skills injection, parallel and chained tool calls, retries, memory.load_mcp— Read an mcp.json, connect every local stdio and remote streamable-HTTP server, expose each server tool as a Tool.parse_mcp_config— Parse and validate an mcp.json without connecting to anything.load_skills— Glob a skills directory and expose one skill tool with progressive disclosure.create_builtin_tools— Construct every built-in tool directly, outside a toolkit.agent— Point at a remote agent’s card and use it exactly like a local tool.WaitFor— The single hook where the host resolves a suspension — in-process prompt or durable queue, same contract.