toolplane 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. toolplane-0.1.0/.github/workflows/ci.yml +36 -0
  2. toolplane-0.1.0/.github/workflows/pages.yml +59 -0
  3. toolplane-0.1.0/.gitignore +13 -0
  4. toolplane-0.1.0/AGENTS.md +63 -0
  5. toolplane-0.1.0/Makefile +52 -0
  6. toolplane-0.1.0/PKG-INFO +279 -0
  7. toolplane-0.1.0/README.md +255 -0
  8. toolplane-0.1.0/ROADMAP.md +105 -0
  9. toolplane-0.1.0/docs/architecture.md +224 -0
  10. toolplane-0.1.0/docs/code-mode-backends.md +252 -0
  11. toolplane-0.1.0/docs/development/release-checklist.md +31 -0
  12. toolplane-0.1.0/docs/index.md +180 -0
  13. toolplane-0.1.0/examples/README.md +39 -0
  14. toolplane-0.1.0/examples/ambient_cli_git.py +25 -0
  15. toolplane-0.1.0/examples/context7_remote.py +52 -0
  16. toolplane-0.1.0/examples/fastmcp_in_process.py +36 -0
  17. toolplane-0.1.0/examples/mcp_stdio_config.py +40 -0
  18. toolplane-0.1.0/examples/mcp_stdio_server.py +17 -0
  19. toolplane-0.1.0/examples/mixed_capability_report.py +142 -0
  20. toolplane-0.1.0/examples/scoped_namespaces_context7.py +153 -0
  21. toolplane-0.1.0/mkdocs.yml +13 -0
  22. toolplane-0.1.0/pyproject.toml +41 -0
  23. toolplane-0.1.0/src/toolplane/__init__.py +46 -0
  24. toolplane-0.1.0/src/toolplane/adapters/__init__.py +13 -0
  25. toolplane-0.1.0/src/toolplane/adapters/ambient_cli.py +329 -0
  26. toolplane-0.1.0/src/toolplane/adapters/cli_to_py.py +223 -0
  27. toolplane-0.1.0/src/toolplane/adapters/mcp.py +208 -0
  28. toolplane-0.1.0/src/toolplane/adapters/python.py +46 -0
  29. toolplane-0.1.0/src/toolplane/backends/__init__.py +7 -0
  30. toolplane-0.1.0/src/toolplane/backends/_python.py +12 -0
  31. toolplane-0.1.0/src/toolplane/backends/base.py +27 -0
  32. toolplane-0.1.0/src/toolplane/backends/local.py +178 -0
  33. toolplane-0.1.0/src/toolplane/backends/pyodide_deno.py +519 -0
  34. toolplane-0.1.0/src/toolplane/bridges/__init__.py +14 -0
  35. toolplane-0.1.0/src/toolplane/bridges/base.py +49 -0
  36. toolplane-0.1.0/src/toolplane/bridges/in_process.py +38 -0
  37. toolplane-0.1.0/src/toolplane/bridges/rpc.py +116 -0
  38. toolplane-0.1.0/src/toolplane/capabilities.py +190 -0
  39. toolplane-0.1.0/src/toolplane/discovery.py +91 -0
  40. toolplane-0.1.0/src/toolplane/errors.py +27 -0
  41. toolplane-0.1.0/src/toolplane/execution.py +50 -0
  42. toolplane-0.1.0/src/toolplane/registry.py +245 -0
  43. toolplane-0.1.0/src/toolplane/runtime.py +194 -0
  44. toolplane-0.1.0/tests/test_ambient_cli.py +97 -0
  45. toolplane-0.1.0/tests/test_bridges.py +154 -0
  46. toolplane-0.1.0/tests/test_cli_to_py_adapter.py +198 -0
  47. toolplane-0.1.0/tests/test_mcp_adapter.py +330 -0
  48. toolplane-0.1.0/tests/test_pyodide_deno_backend.py +132 -0
  49. toolplane-0.1.0/tests/test_toolplane.py +188 -0
@@ -0,0 +1,36 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ jobs:
10
+ test:
11
+ name: Python ${{ matrix.python-version }}
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python-version:
17
+ - "3.11"
18
+ - "3.12"
19
+ - "3.13"
20
+
21
+ steps:
22
+ - name: Check out repository
23
+ uses: actions/checkout@v4
24
+
25
+ - name: Set up Python
26
+ uses: actions/setup-python@v5
27
+ with:
28
+ python-version: ${{ matrix.python-version }}
29
+
30
+ - name: Set up uv
31
+ uses: astral-sh/setup-uv@v5
32
+ with:
33
+ enable-cache: true
34
+
35
+ - name: Run tests, examples, and docs
36
+ run: make ci
@@ -0,0 +1,59 @@
1
+ name: Docs
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+ pages: write
13
+ id-token: write
14
+
15
+ concurrency:
16
+ group: pages
17
+ cancel-in-progress: false
18
+
19
+ jobs:
20
+ build:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - name: Check out repository
24
+ uses: actions/checkout@v4
25
+
26
+ - name: Set up Python
27
+ uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.12"
30
+
31
+ - name: Set up uv
32
+ uses: astral-sh/setup-uv@v5
33
+ with:
34
+ enable-cache: true
35
+
36
+ - name: Build docs
37
+ run: make docs SITE_DIR=site
38
+
39
+ - name: Configure Pages
40
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
41
+ uses: actions/configure-pages@v5
42
+
43
+ - name: Upload Pages artifact
44
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
45
+ uses: actions/upload-pages-artifact@v3
46
+ with:
47
+ path: site
48
+
49
+ deploy:
50
+ needs: build
51
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
52
+ runs-on: ubuntu-latest
53
+ environment:
54
+ name: github-pages
55
+ url: ${{ steps.deployment.outputs.page_url }}
56
+ steps:
57
+ - name: Deploy to GitHub Pages
58
+ id: deployment
59
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .Python
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .mypy_cache/
7
+ .venv/
8
+ .omx/
9
+ uv.lock
10
+ dist/
11
+ build/
12
+ *.egg-info/
13
+ .DS_Store
@@ -0,0 +1,63 @@
1
+ # toolplane Agent Guidance
2
+
3
+ This repo exists to build a controlled Python code-mode runtime where CLIs, MCP
4
+ tools, and libraries are normalized into one programmable tool surface.
5
+
6
+ ## Product Direction
7
+
8
+ - Keep `toolplane` focused on the programmable tool surface, not a full agent
9
+ framework.
10
+ - Preserve the stable flow:
11
+
12
+ ```text
13
+ discover capabilities -> inspect schemas -> execute Python against a curated namespace
14
+ ```
15
+
16
+ - Keep discovery and execution separate. Backends execute code; registries
17
+ describe and dispatch capabilities.
18
+ - Treat MCP tools, CLI wrappers, Python functions, and host helpers as
19
+ capability adapters, not execution backends.
20
+
21
+ ## Implementation Bias
22
+
23
+ - Build small vertical slices and validate them empirically.
24
+ - Avoid speculative plugin frameworks or broad abstractions before concrete
25
+ backends force them.
26
+ - Let each piece of code earn its place through behavior, tests, or an imminent
27
+ integration need.
28
+ - Keep tests focused on contracts and empirical behavior. No ceremonial testing.
29
+
30
+ ## Pydantic vs Dataclass
31
+
32
+ Dataclasses are fine for simple internal value carriers.
33
+
34
+ Prefer Pydantic earlier when a model is likely to cross a boundary soon, not only
35
+ in a distant future. Boundary-crossing models include:
36
+
37
+ - execution results and structured errors
38
+ - backend capability/config objects
39
+ - capability manifests loaded from config or remote registries
40
+ - data sent over sandbox RPC
41
+ - adapter inputs/outputs that need validation or JSON schema export
42
+
43
+ If a dataclass is starting to need manual validation, serialization,
44
+ deserialization, or schema generation, move it to Pydantic instead of layering
45
+ more custom code around it.
46
+
47
+ ## Backend Direction
48
+
49
+ - `local_unsafe` is only for development and shape validation.
50
+ - Monty is useful for safe small-tool orchestration, but it is not the default
51
+ answer for package-heavy code.
52
+ - Pyodide+Deno is the next likely default sandbox for package-capable snippets,
53
+ especially pandas/NumPy-style workflows.
54
+ - Docker, Modal, E2B, and Blaxel are needed for arbitrary CPython packages,
55
+ native system dependencies, subprocesses, GPUs, remote isolation, or
56
+ long-running jobs.
57
+
58
+ ## Verification
59
+
60
+ - Prefer empirical smoke tests alongside unit tests.
61
+ - For backend work, prove the real execution path works, not just config
62
+ construction.
63
+ - Keep artifacts out of the repo unless they are intentional source files.
@@ -0,0 +1,52 @@
1
+ .PHONY: help test examples docs docs-serve ci build clean publish-check publish
2
+
3
+ UV ?= uv --no-config
4
+ PYTEST ?= pytest
5
+ SITE_DIR ?= /tmp/toolplane-site
6
+ PORT ?= 8000
7
+ PYPI_CHECK_URL ?= https://pypi.org/simple/
8
+
9
+ help:
10
+ @printf "toolplane commands\n"
11
+ @printf " make test Run the pytest suite\n"
12
+ @printf " make examples Run deterministic example smokes\n"
13
+ @printf " make docs Build MkDocs in strict mode\n"
14
+ @printf " make docs-serve Serve docs locally on PORT=%s\n" "$(PORT)"
15
+ @printf " make ci Run tests and strict docs build\n"
16
+ @printf " make build Build package artifacts\n"
17
+ @printf " make publish-check Dry-run publish built artifacts\n"
18
+ @printf " make publish Publish built artifacts to PyPI\n"
19
+ @printf " make clean Remove local generated artifacts\n"
20
+
21
+ test:
22
+ $(UV) run --no-project --with-editable ".[dev]" python -m $(PYTEST)
23
+
24
+ examples:
25
+ $(UV) run --no-project --with-editable . python examples/ambient_cli_git.py
26
+ $(UV) run --no-project --with-editable . python examples/fastmcp_in_process.py
27
+ $(UV) run --no-project --with-editable . python examples/mcp_stdio_config.py
28
+
29
+ docs:
30
+ $(UV) run --no-project --with-editable ".[docs]" mkdocs build --strict --site-dir $(SITE_DIR)
31
+
32
+ docs-serve:
33
+ $(UV) run --no-project --with-editable ".[docs]" mkdocs serve -a 127.0.0.1:$(PORT)
34
+
35
+ ci: test examples docs
36
+
37
+ build:
38
+ $(UV) build
39
+
40
+ publish-check: build
41
+ @if [ -n "$$PYPI_TOKEN" ]; then \
42
+ UV_PUBLISH_TOKEN="$$PYPI_TOKEN" $(UV) publish --dry-run --check-url $(PYPI_CHECK_URL) dist/*; \
43
+ else \
44
+ $(UV) publish --dry-run --check-url $(PYPI_CHECK_URL) dist/*; \
45
+ fi
46
+
47
+ publish: build
48
+ @test -n "$(PYPI_TOKEN)" || (printf '%s\n' 'PYPI_TOKEN is required' >&2; exit 1)
49
+ @UV_PUBLISH_TOKEN="$(PYPI_TOKEN)" $(UV) publish --check-url $(PYPI_CHECK_URL) dist/*
50
+
51
+ clean:
52
+ rm -rf .pytest_cache build dist site *.egg-info
@@ -0,0 +1,279 @@
1
+ Metadata-Version: 2.4
2
+ Name: toolplane
3
+ Version: 0.1.0
4
+ Summary: A controlled Python code-mode runtime for programmable tool surfaces.
5
+ License: MIT
6
+ Keywords: agents,cli,code-mode,mcp,runtime,tools
7
+ Classifier: Development Status :: 2 - Pre-Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Requires-Python: >=3.11
16
+ Requires-Dist: cli-to-py>=0.1
17
+ Requires-Dist: fastmcp>=3.1
18
+ Requires-Dist: pydantic>=2.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=8; extra == 'dev'
21
+ Provides-Extra: docs
22
+ Requires-Dist: mkdocs>=1.6; extra == 'docs'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # toolplane
26
+
27
+ [![PyPI version](https://img.shields.io/pypi/v/toolplane)](https://pypi.org/project/toolplane/)
28
+ [![Python versions](https://img.shields.io/pypi/pyversions/toolplane)](https://pypi.org/project/toolplane/)
29
+ [![CI](https://github.com/oneryalcin/toolplane/actions/workflows/ci.yml/badge.svg)](https://github.com/oneryalcin/toolplane/actions/workflows/ci.yml)
30
+ [![Docs](https://github.com/oneryalcin/toolplane/actions/workflows/pages.yml/badge.svg)](https://github.com/oneryalcin/toolplane/actions/workflows/pages.yml)
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
32
+
33
+ A controlled Python code-mode runtime where CLIs, MCP tools, and libraries are
34
+ normalized into one programmable tool surface.
35
+
36
+ Full documentation: https://oneryalcin.github.io/toolplane/
37
+
38
+ ## Why It Exists
39
+
40
+ Agents are strongest when they can use code as the control plane for real work:
41
+ looping, branching, filtering, retrying, aggregating, and combining tools
42
+ without bouncing through one tool call at a time.
43
+
44
+ Python already has the right orchestration model. What is missing is a clean
45
+ way to expose different tool sources through one curated runtime:
46
+
47
+ - CLI tools wrapped as Python callables.
48
+ - MCP server tools exposed as async Python functions.
49
+ - Regular Python libraries such as `pandas`, `httpx`, and project SDKs.
50
+ - Host-provided domain helpers with explicit permissions and limits.
51
+
52
+ `toolplane` is the runtime layer for that surface. The agent writes Python; the
53
+ host decides which capabilities exist, how credentials are handled, and what
54
+ resource or security boundaries apply.
55
+
56
+ ## Relationship To cli-to-py
57
+
58
+ [`cli-to-py`](https://github.com/oneryalcin/cli-to-py) turns CLI binaries into
59
+ Python APIs. In `toolplane`, that is one adapter in a broader adapter stack.
60
+
61
+ The goal is that agent-written code should not need to care whether a capability
62
+ came from a CLI, an MCP server, or a normal Python package. It should see typed,
63
+ validated Python functions with predictable return values.
64
+
65
+ ## Prior Art
66
+
67
+ [FastMCP Code Mode](https://gofastmcp.com/servers/transforms/code-mode) is a
68
+ strong reference point. It replaces a large MCP tool catalog with a smaller set
69
+ of meta-tools for progressive discovery and code execution: search for relevant
70
+ tools, inspect the schemas that matter, then execute Python that orchestrates
71
+ tool calls in a sandbox.
72
+
73
+ `toolplane` follows the same basic shape:
74
+
75
+ ```text
76
+ discover capabilities -> inspect schemas -> execute Python against a curated namespace
77
+ ```
78
+
79
+ The difference is scope. FastMCP Code Mode is centered on MCP server tools.
80
+ `toolplane` aims to generalize that pattern across MCP tools, CLI wrappers, and
81
+ regular Python libraries.
82
+
83
+ [OpenAI Agents SDK sandboxes](https://developers.openai.com/api/docs/guides/agents/sandboxes)
84
+ are another useful reference: they separate the sandbox session/provider from
85
+ the tools exposed to the model. Toolplane follows that boundary too. Backends
86
+ execute code; adapters expose capabilities; bridges let sandboxed code call host
87
+ capabilities when direct local calls are not appropriate.
88
+
89
+ See [Code Mode Backends](docs/code-mode-backends.md) for the initial backend
90
+ strategy and [Architecture](docs/architecture.md) for the code organization
91
+ approach.
92
+
93
+ See [ROADMAP.md](ROADMAP.md) for the current sequencing.
94
+
95
+ ## Design Goals
96
+
97
+ - Make code-mode agents useful for multi-step tool orchestration.
98
+ - Normalize heterogeneous tools into a Python-first API surface.
99
+ - Keep the exposed runtime curated rather than ambiently powerful.
100
+ - Preserve host control over credentials, authorization, filesystems, network
101
+ access, timeouts, and cancellation.
102
+ - Prefer structured return values and validation errors over raw text where
103
+ practical.
104
+ - Keep adapters small enough to be understandable and replaceable.
105
+ - Treat JSON as a wire format, not the programming model. Agent-written code
106
+ should compose normal Python values and callables.
107
+ - Make canonical capability ids qualified, and expose friendly Python aliases
108
+ only when they are unambiguous.
109
+
110
+ ## Docs
111
+
112
+ ```bash
113
+ make docs
114
+ make docs-serve
115
+ ```
116
+
117
+ ## Development
118
+
119
+ ```bash
120
+ make test
121
+ make examples
122
+ make ci
123
+ make publish-check
124
+ ```
125
+
126
+ Publishing uses the same local release surface:
127
+
128
+ ```bash
129
+ PYPI_TOKEN=... make publish
130
+ ```
131
+
132
+ See the [release checklist](docs/development/release-checklist.md) for the full
133
+ publish flow.
134
+
135
+ ## Status
136
+
137
+ Early implementation. Toolplane can register Python functions, explicit
138
+ `cli-to-py` wrappers, and FastMCP-backed MCP tools, then discover them, inspect
139
+ schemas, and execute agent-written Python through:
140
+
141
+ - `local_unsafe`: development-only in-process execution.
142
+ - `pyodide-deno`: experimental Pyodide-in-Deno sandbox execution with package
143
+ loading and host bridge `call_tool` callbacks.
144
+
145
+ ```python
146
+ from toolplane import Toolplane
147
+
148
+ runtime = Toolplane()
149
+
150
+ @runtime.tool(tags={"math"})
151
+ def add(x: int, y: int) -> int:
152
+ """Add two numbers."""
153
+ return x + y
154
+
155
+ result = await runtime.execute("""
156
+ value = await call_tool("add", {"x": 2, "y": 3})
157
+ return value
158
+ """)
159
+ ```
160
+
161
+ The current Pyodide+Deno smoke target works with pandas:
162
+
163
+ ```python
164
+ result = await runtime.execute(
165
+ """
166
+ import pandas as pd
167
+
168
+ x = await call_tool("add", {"x": 2, "y": 3})
169
+ df = pd.DataFrame([{"value": x}])
170
+ return int(df["value"].sum())
171
+ """,
172
+ backend="pyodide-deno",
173
+ packages=["pandas"],
174
+ )
175
+ ```
176
+
177
+ CLI tools are also available through ambient lazy proxies in the execution
178
+ namespace. Toolplane does not parse every binary at startup; it resolves a CLI
179
+ through `cli-to-py` only when code first calls it:
180
+
181
+ ```python
182
+ result = await runtime.execute("""
183
+ status = await git.status(short=True).text()
184
+ files = await git.diff(name_only=True, _=["HEAD~1", "HEAD"]).lines()
185
+ return {"status": status, "files": files}
186
+ """)
187
+ ```
188
+
189
+ For binaries that are not valid Python identifiers, use the `cli` root:
190
+
191
+ ```python
192
+ result = await runtime.execute("""
193
+ version = await cli("docker-compose").version().text()
194
+ return version
195
+ """)
196
+ ```
197
+
198
+ CLI tools can be exposed as capabilities during host setup:
199
+
200
+ ```python
201
+ from cli_to_py import convert
202
+ from toolplane import Toolplane
203
+
204
+ runtime = Toolplane()
205
+ python = await convert("python3", subcommands=False)
206
+
207
+ runtime.register_cli(
208
+ "python_version",
209
+ python,
210
+ description="Return the Python interpreter version.",
211
+ tags={"python", "cli"},
212
+ )
213
+
214
+ result = await runtime.execute("""
215
+ version = await call_tool("python_version", {"version": True})
216
+ return version["stdout"] + version["stderr"]
217
+ """)
218
+ ```
219
+
220
+ MCP servers can be exposed the same way. An in-process FastMCP app:
221
+
222
+ ```python
223
+ from fastmcp import FastMCP
224
+ from toolplane import Toolplane
225
+
226
+ runtime = Toolplane()
227
+ mcp = FastMCP("Demo")
228
+
229
+ @mcp.tool
230
+ def add(a: int, b: int) -> int:
231
+ """Add two numbers."""
232
+ return a + b
233
+
234
+ await runtime.register_mcp("demo", mcp)
235
+
236
+ result = await runtime.execute("""
237
+ value = await demo.add(a=2, b=3)
238
+ return value
239
+ """)
240
+ ```
241
+
242
+ Or a standard `mcpServers` config, including stdio or remote HTTP servers:
243
+
244
+ ```python
245
+ await runtime.register_mcp_config({
246
+ "mcpServers": {
247
+ "context7": {
248
+ "url": "https://mcp.context7.com/mcp",
249
+ }
250
+ }
251
+ })
252
+ ```
253
+
254
+ Registered MCP tools get canonical ids such as `mcp:context7/get_docs` and safe
255
+ Python aliases such as `context7_get_docs`. They are also available through a
256
+ scoped namespace, so agent-written code can call `context7.get_docs(...)`
257
+ without caring that the capability came from MCP.
258
+
259
+ Host Python helpers can be grouped the same way:
260
+
261
+ ```python
262
+ from pathlib import Path
263
+ from toolplane import Toolplane
264
+
265
+ runtime = Toolplane()
266
+
267
+ def read_text(path: str) -> str:
268
+ return Path(path).read_text()
269
+
270
+ runtime.register_python_namespace("repo", {"read_text": read_text})
271
+
272
+ result = await runtime.execute("""
273
+ text = await repo.read_text(path="README.md")
274
+ return text.splitlines()[0]
275
+ """)
276
+ ```
277
+
278
+ See [examples](examples/README.md) for executable FastMCP in-process, stdio
279
+ config, and live Context7 remote MCP smokes.