A controlled Python code-mode runtime for programmable tool surfaces.
Project description
toolplane
A controlled Python code-mode runtime where CLIs, MCP tools, and libraries are normalized into one programmable tool surface.
Full documentation: https://oneryalcin.github.io/toolplane/
Why It Exists
Agents are strongest when they can use code as the control plane for real work: looping, branching, filtering, retrying, aggregating, and combining tools without bouncing through one tool call at a time.
Python already has the right orchestration model. What is missing is a clean way to expose different tool sources through one curated runtime:
- CLI tools wrapped as Python callables.
- MCP server tools exposed as async Python functions.
- Regular Python libraries such as
pandas,httpx, and project SDKs. - Host-provided domain helpers with explicit permissions and limits.
toolplane is the runtime layer for that surface. The agent writes Python; the
host decides which capabilities exist, how credentials are handled, and what
resource or security boundaries apply.
Relationship To cli-to-py
cli-to-py turns CLI binaries into
Python APIs. In toolplane, that is one adapter in a broader adapter stack.
The goal is that agent-written code should not need to care whether a capability came from a CLI, an MCP server, or a normal Python package. It should see typed, validated Python functions with predictable return values.
Prior Art
FastMCP Code Mode is a strong reference point. It replaces a large MCP tool catalog with a smaller set of meta-tools for progressive discovery and code execution: search for relevant tools, inspect the schemas that matter, then execute Python that orchestrates tool calls in a sandbox.
toolplane follows the same basic shape:
discover capabilities -> inspect schemas -> execute Python against a curated namespace
The difference is scope. FastMCP Code Mode is centered on MCP server tools.
toolplane aims to generalize that pattern across MCP tools, CLI wrappers, and
regular Python libraries.
OpenAI Agents SDK sandboxes are another useful reference: they separate the sandbox session/provider from the tools exposed to the model. Toolplane follows that boundary too. Backends execute code; adapters expose capabilities; bridges let sandboxed code call host capabilities when direct local calls are not appropriate.
See Code Mode Backends for the initial backend strategy and Architecture for the code organization approach.
See ROADMAP.md for the current sequencing.
Design Goals
- Make code-mode agents useful for multi-step tool orchestration.
- Normalize heterogeneous tools into a Python-first API surface.
- Keep the exposed runtime curated rather than ambiently powerful.
- Preserve host control over credentials, authorization, filesystems, network access, timeouts, and cancellation.
- Prefer structured return values and validation errors over raw text where practical.
- Keep adapters small enough to be understandable and replaceable.
- Treat JSON as a wire format, not the programming model. Agent-written code should compose normal Python values and callables.
- Make canonical capability ids qualified, and expose friendly Python aliases only when they are unambiguous.
Docs
make docs
make docs-serve
Development
make test
make examples
make ci
make publish-check
Publishing uses the same local release surface:
PYPI_TOKEN=... make publish
See the release checklist for the full publish flow.
Status
Early implementation. Toolplane can register Python functions, explicit
cli-to-py wrappers, and FastMCP-backed MCP tools, then discover them, inspect
schemas, and execute agent-written Python through:
local_unsafe: development-only in-process execution.pyodide-deno: experimental Pyodide-in-Deno sandbox execution with package loading and host bridgecall_toolcallbacks.
from toolplane import Toolplane
runtime = Toolplane()
@runtime.tool(tags={"math"})
def add(x: int, y: int) -> int:
"""Add two numbers."""
return x + y
result = await runtime.execute("""
value = await call_tool("add", {"x": 2, "y": 3})
return value
""")
The current Pyodide+Deno smoke target works with pandas:
result = await runtime.execute(
"""
import pandas as pd
x = await call_tool("add", {"x": 2, "y": 3})
df = pd.DataFrame([{"value": x}])
return int(df["value"].sum())
""",
backend="pyodide-deno",
packages=["pandas"],
)
CLI tools are also available through ambient lazy proxies in the execution
namespace. Toolplane does not parse every binary at startup; it resolves a CLI
through cli-to-py only when code first calls it:
result = await runtime.execute("""
status = await git.status(short=True).text()
files = await git.diff(name_only=True, _=["HEAD~1", "HEAD"]).lines()
return {"status": status, "files": files}
""")
For binaries that are not valid Python identifiers, use the cli root:
result = await runtime.execute("""
version = await cli("docker-compose").version().text()
return version
""")
CLI tools can be exposed as capabilities during host setup:
from cli_to_py import convert
from toolplane import Toolplane
runtime = Toolplane()
python = await convert("python3", subcommands=False)
runtime.register_cli(
"python_version",
python,
description="Return the Python interpreter version.",
tags={"python", "cli"},
)
result = await runtime.execute("""
version = await call_tool("python_version", {"version": True})
return version["stdout"] + version["stderr"]
""")
MCP servers can be exposed the same way. An in-process FastMCP app:
from fastmcp import FastMCP
from toolplane import Toolplane
runtime = Toolplane()
mcp = FastMCP("Demo")
@mcp.tool
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
await runtime.register_mcp("demo", mcp)
result = await runtime.execute("""
value = await demo.add(a=2, b=3)
return value
""")
Or a standard mcpServers config, including stdio or remote HTTP servers:
await runtime.register_mcp_config({
"mcpServers": {
"context7": {
"url": "https://mcp.context7.com/mcp",
}
}
})
Registered MCP tools get canonical ids such as mcp:context7/get_docs and safe
Python aliases such as context7_get_docs. They are also available through a
scoped namespace, so agent-written code can call context7.get_docs(...)
without caring that the capability came from MCP.
Host Python helpers can be grouped the same way:
from pathlib import Path
from toolplane import Toolplane
runtime = Toolplane()
def read_text(path: str) -> str:
return Path(path).read_text()
runtime.register_python_namespace("repo", {"read_text": read_text})
result = await runtime.execute("""
text = await repo.read_text(path="README.md")
return text.splitlines()[0]
""")
See examples for executable FastMCP in-process, stdio config, and live Context7 remote MCP smokes.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file toolplane-0.1.0.tar.gz.
File metadata
- Download URL: toolplane-0.1.0.tar.gz
- Upload date:
- Size: 43.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b612da6bce74a5dbe5f0d81329e56d637666663bb1f7f90087f0f75f7094ea72
|
|
| MD5 |
1591af09f69f25bb59fc8774d02864eb
|
|
| BLAKE2b-256 |
909b80a24cf1fcecf1a074284a7408144c412c3c24b085847c5149891654ef74
|
File details
Details for the file toolplane-0.1.0-py3-none-any.whl.
File metadata
- Download URL: toolplane-0.1.0-py3-none-any.whl
- Upload date:
- Size: 31.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c0e9ee6c662c24fc5d33f3f383ada1d1459d66458795989a74e475e1d52ca3f8
|
|
| MD5 |
228e17908ce66d56ab71d381d2a2b510
|
|
| BLAKE2b-256 |
c95ceb468c7ae663f3246a1eca82706581479bcf6b496156a3ab8d80e647ea16
|